Skip to content

Add ability to specify idbag (#415) #755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
Expand Down Expand Up @@ -333,4 +334,88 @@ public void CanSpecifyMultipleChildKeyColumns()
.Element("class/bag/many-to-many/column[@name='ID1']").Exists()
.Element("class/bag/many-to-many/column[@name='ID2']").Exists();
}

[Test]
public void CanSpecifyIdBag()
{
new MappingTester<ManyToManyTarget>()
.ForMapping(m => m.HasManyToMany(x => x.BagOfChildren)
.AsIdBag<int>(x => x.Column("Id")))
.Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0)
.HasAttribute("column", "Id")
.HasAttribute("type", typeof(int).AssemblyQualifiedName)
.Element("class/idbag/many-to-many").Exists();
}

[Test]
public void CanSpecifyIdBagWithLength()
{
new MappingTester<ManyToManyTarget>()
.ForMapping(m => m.HasManyToMany(x => x.BagOfChildren)
.AsIdBag<string>(x => x.Column("Id").Length(10)))
.Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0)
.HasAttribute("column", "Id")
.HasAttribute("type", typeof(string).AssemblyQualifiedName)
.HasAttribute("length", "10")
.Element("class/idbag/collection-id/generator").Exists()
.HasAttribute("class", "assigned");
}

[Test]
public void CanSpecifyIdBagWithNonGenericType()
{
new MappingTester<ManyToManyTarget>()
.ForMapping(m => m.HasManyToMany(x => x.BagOfChildren)
.AsIdBag(typeof(string)))
.Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0)
.HasAttribute("type", typeof(string).AssemblyQualifiedName);
}

[Test]
public void CanSpecifyIdBagWithGenerator()
{
new MappingTester<ManyToManyTarget>()
.ForMapping(m => m.HasManyToMany(x => x.BagOfChildren)
.AsIdBag(typeof(int), x => x.GeneratedBy.Identity()))
.Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0)
.Element("class/idbag/collection-id/generator").Exists()
.HasAttribute("class", "identity");
}

[Test]
public void CanSpecifyIdBagWithDefaultIdType()
{
new MappingTester<ManyToManyTarget>()
.ForMapping(m => m.HasManyToMany(x => x.BagOfChildren)
.AsIdBag())
.Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0)
.HasAttribute("type", typeof(int).AssemblyQualifiedName);
}

[Test]
public void CanSpecifyIdBagWithCompositeElement()
{
new MappingTester<ManyToManyTarget>()
.ForMapping(m => m.HasManyToMany(x => x.BagOfChildren)
.Component(c =>
{
c.Map(x => x.Name);
c.Map(x => x.Active);
})
.AsIdBag())
.Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0)
.Element("class/idbag/composite-element").Exists()
.Element("class/idbag/composite-element/property[@name='Name']").Exists();
}

[Test]
public void CanSpecifyIdBagWithElement()
{
var x = new MappingTester<ManyToManyTarget>()
.ForMapping(m => m.HasManyToMany(x => x.ListOfSimpleChildren)
.Element("Name")
.AsIdBag());
x.Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0)
.Element("class/idbag/element").Exists();
}
}
12 changes: 1 addition & 11 deletions src/FluentNHibernate/Automapping/Steps/IdentityStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ namespace FluentNHibernate.Automapping.Steps;

public class IdentityStep(IAutomappingConfiguration cfg) : IAutomappingStep
{
readonly List<Type> identityCompatibleTypes = new List<Type> { typeof(long), typeof(int), typeof(short), typeof(byte) };

public bool ShouldMap(Member member)
{
return cfg.IsId(member);
Expand Down Expand Up @@ -52,15 +50,7 @@ void SetDefaultAccess(Member member, IdMapping mapping)
GeneratorMapping GetDefaultGenerator(Member property)
{
var generatorMapping = new GeneratorMapping();
var defaultGenerator = new GeneratorBuilder(generatorMapping, property.PropertyType, Layer.Defaults);

if (property.PropertyType == typeof(Guid))
defaultGenerator.GuidComb();
else if (identityCompatibleTypes.Contains(property.PropertyType))
defaultGenerator.Identity();
else
defaultGenerator.Assigned();

new GeneratorBuilder(generatorMapping, property.PropertyType, Layer.Defaults).SetDefault();
return generatorMapping;
}
}
70 changes: 70 additions & 0 deletions src/FluentNHibernate/Mapping/CollectionIdPart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using FluentNHibernate.Mapping.Providers;
using FluentNHibernate.MappingModel;
using FluentNHibernate.MappingModel.Collections;
using FluentNHibernate.MappingModel.Identity;

namespace FluentNHibernate.Mapping;

public class CollectionIdPart : ICollectionIdMappingProvider
{
readonly Type entity;
readonly AttributeStore attributes = new();

/// <summary>
/// Specify the generator
/// </summary>
/// <example>
/// .AsIdBag&lt;int&gt;(x => x.Column("Id").GeneratedBy.Identity())
/// </example>
public IdentityGenerationStrategyBuilder<CollectionIdPart> GeneratedBy { get; }

public CollectionIdPart(Type entityType, Type idColumnType)
{
attributes.Set("Type", Layer.UserSupplied, new TypeReference(idColumnType));
entity = entityType;
GeneratedBy = new IdentityGenerationStrategyBuilder<CollectionIdPart>(this, idColumnType, entityType);
SetDefaultGenerator(idColumnType);
}

/// <summary>
/// Specifies the id column length
/// </summary>
/// <param name="length">Column length</param>
public CollectionIdPart Length(int length)
{
attributes.Set("Length", Layer.UserSupplied, length);
return this;
}

/// <summary>
/// Specifies the column name for the collection id.
/// </summary>
/// <param name="idColumnName">Column name</param>
public CollectionIdPart Column(string idColumnName)
{
attributes.Set("Column", Layer.UserSupplied, idColumnName);
return this;
}

void SetDefaultGenerator(Type idColumnType)
{
var generatorMapping = new GeneratorMapping();
new GeneratorBuilder(generatorMapping, idColumnType, Layer.UserSupplied).SetDefault();
attributes.Set("Generator", Layer.Defaults, generatorMapping);
}

CollectionIdMapping ICollectionIdMappingProvider.GetCollectionIdMapping()
{
var mapping = new CollectionIdMapping(attributes.Clone())
{
ContainingEntityType = entity
};

mapping.Set(x => x.Column, Layer.Defaults, "Id");
if (GeneratedBy.IsDirty)
mapping.Set(x => x.Generator, Layer.UserSupplied, GeneratedBy.GetGeneratorMapping());

return mapping;
}
}
13 changes: 13 additions & 0 deletions src/FluentNHibernate/Mapping/GeneratorBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using FluentNHibernate.MappingModel.Identity;
using NHibernate.Id;

namespace FluentNHibernate.Mapping;

class GeneratorBuilder(GeneratorMapping mapping, Type identityType, int layer)
{
readonly HashSet<Type> identityCompatibleTypes = new HashSet<Type>{ typeof(long), typeof(int), typeof(short), typeof(byte) };

void SetGenerator(string generator)
{
mapping.Set(x => x.Class, layer, generator);
Expand All @@ -31,6 +34,16 @@ void EnsureStringIdentityType()
if (identityType != typeof(string)) throw new InvalidOperationException("Identity type must be string");
}

internal void SetDefault()
{
if (identityType == typeof(Guid))
GuidComb();
else if (identityCompatibleTypes.Contains(identityType))
Identity();
else
Assigned();
}

static bool IsIntegralType(Type t)
{
// do we think we'll encounter more?
Expand Down
10 changes: 1 addition & 9 deletions src/FluentNHibernate/Mapping/IdentityPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,7 @@ internal void SetName(string newName)
void SetDefaultGenerator()
{
var generatorMapping = new GeneratorMapping();
var defaultGenerator = new GeneratorBuilder(generatorMapping, identityType, Layer.UserSupplied);

if (identityType == typeof(Guid))
defaultGenerator.GuidComb();
else if (identityType == typeof(int) || identityType == typeof(long))
defaultGenerator.Identity();
else
defaultGenerator.Assigned();

new GeneratorBuilder(generatorMapping, identityType, Layer.UserSupplied).SetDefault();
attributes.Set("Generator", Layer.Defaults, generatorMapping);
}

Expand Down
31 changes: 31 additions & 0 deletions src/FluentNHibernate/Mapping/ManyToManyPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class ManyToManyPart<TChild> : ToManyBase<ManyToManyPart<TChild>, TChild>
readonly Type childType;
Type valueType;
bool isTernary;
CollectionIdMapping collectionIdMapping;

public ManyToManyPart(Type entity, Member property)
: this(entity, property, property.PropertyType)
Expand Down Expand Up @@ -338,11 +339,41 @@ public ManyToManyPart<TChild> ChildWhere(Expression<Func<TChild, bool>> where)
{
return ChildWhere(ExpressionToSql.Convert(@where));
}

/// <summary>
/// Use an idbag collection
/// </summary>
public ManyToManyPart<TChild> AsIdBag<TIdType>(Action<CollectionIdPart> customId = null)
{
return AsIdBag(typeof(TIdType), customId);
}

/// <summary>
/// Use an idbag collection with an int id type
/// </summary>
public ManyToManyPart<TChild> AsIdBag(Action<CollectionIdPart> customId = null)
{
return AsIdBag(typeof(Int32), customId);
}

/// <summary>
/// Use an idbag collection
/// </summary>
public ManyToManyPart<TChild> AsIdBag(Type idColType, Action<CollectionIdPart> customId = null)
{
collectionBuilder = CollectionMapping.IdBag;
var builder = new CollectionIdPart(typeof(ManyToManyPart<TChild>), idColType);
customId?.Invoke(builder);
collectionIdMapping = ((ICollectionIdMappingProvider)builder).GetCollectionIdMapping();
return this;
}

protected override CollectionMapping GetCollectionMapping()
{
var collection = base.GetCollectionMapping();

collection.Set(x => x.CollectionId, Layer.Defaults, collectionIdMapping);

// key columns
if (ParentKeyColumns.Count == 0)
collection.Key.AddColumn(Layer.Defaults, new ColumnMapping(EntityType.Name + "_id"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using FluentNHibernate.MappingModel.Collections;

namespace FluentNHibernate.Mapping.Providers;

public interface ICollectionIdMappingProvider
{
CollectionIdMapping GetCollectionIdMapping();
}
4 changes: 2 additions & 2 deletions src/FluentNHibernate/Mapping/ToManyBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ public abstract class ToManyBase<T, TChild> : ICollectionMappingProvider
protected ElementPart elementPart;
protected ICompositeElementMappingProvider componentMapping;
protected bool nextBool = true;
protected Func<AttributeStore, CollectionMapping> collectionBuilder;

protected readonly AttributeStore collectionAttributes = new AttributeStore();
protected readonly KeyMapping keyMapping = new KeyMapping();
protected readonly AttributeStore relationshipAttributes = new AttributeStore();
Func<AttributeStore, CollectionMapping> collectionBuilder;
IndexMapping indexMapping;
protected Member member;
readonly List<IFilterMappingProvider> filters = [];
Expand Down Expand Up @@ -725,7 +725,7 @@ protected virtual CollectionMapping GetCollectionMapping()
// HACK: Index only on list and map - shouldn't have to do this!
if (mapping.Collection == Collection.Array || mapping.Collection == Collection.List || mapping.Collection == Collection.Map)
mapping.Set(x => x.Index, Layer.Defaults, indexMapping);

if (elementPart is not null)
{
mapping.Set(x => x.Element, Layer.Defaults, ((IElementMappingProvider)elementPart).GetElementMapping());
Expand Down
3 changes: 2 additions & 1 deletion src/FluentNHibernate/MappingModel/Collections/Collection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ public enum Collection
Bag,
Map,
List,
Set
Set,
IdBag
}
Loading