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 5 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,58 @@ 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.MapOfChildren)
.AsIdBag<int>(x => x.Column("Id").GeneratedBy.Identity()))
.Element("class/idbag/collection-id").Exists()
.HasAttribute("column", "Id")
.HasAttribute("type", typeof(int).AssemblyQualifiedName)
.Element("class/idbag/collection-id/generator").Exists()
.HasAttribute("class", "identity");
}

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

[Test]
public void CanSpecifyIdBagWithGenerator()
{
new MappingTester<ManyToManyTarget>()
.ForMapping(m => m.HasManyToMany(x => x.MapOfChildren)
.AsIdBag(typeof(int), x => x.GeneratedBy.Identity()))
.Element("class/idbag/collection-id").Exists()
.HasAttribute("column", "Id")
.HasAttribute("type", typeof(int).AssemblyQualifiedName)
.Element("class/idbag/collection-id/generator").Exists()
.HasAttribute("class", "identity");
}
}
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
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();
}
23 changes: 23 additions & 0 deletions src/FluentNHibernate/Mapping/ToManyBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public abstract class ToManyBase<T, TChild> : ICollectionMappingProvider
protected readonly AttributeStore relationshipAttributes = new AttributeStore();
Func<AttributeStore, CollectionMapping> collectionBuilder;
IndexMapping indexMapping;
CollectionIdMapping collectionIdMapping;
protected Member member;
readonly List<IFilterMappingProvider> filters = [];

Expand Down Expand Up @@ -154,6 +155,26 @@ public T AsBag()
return (T)this;
}

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

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

/// <summary>
/// Use a list collection
/// </summary>
Expand Down Expand Up @@ -725,6 +746,8 @@ 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);
else if (mapping.Collection == Collection.IdBag)
mapping.Set(x => x.CollectionId, Layer.Defaults, collectionIdMapping);

if (elementPart is not null)
{
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Linq.Expressions;
using FluentNHibernate.MappingModel.Identity;
using FluentNHibernate.Utils;
using FluentNHibernate.Visitors;

namespace FluentNHibernate.MappingModel.Collections;

[Serializable]
public sealed class CollectionIdMapping(AttributeStore attributes) : MappingBase, IEquatable<CollectionIdMapping>
{
readonly AttributeStore attributes = attributes;

public override void AcceptVisitor(IMappingModelVisitor visitor)
{
visitor.ProcessCollectionId(this);
if (Generator is not null)
visitor.Visit(Generator);
}

public GeneratorMapping Generator => attributes.GetOrDefault<GeneratorMapping>();

public string Column => attributes.GetOrDefault<string>();

public int Length => attributes.GetOrDefault<int>();

public TypeReference Type => attributes.GetOrDefault<TypeReference>();

public Type ContainingEntityType { get; set; }

public bool Equals(CollectionIdMapping other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.attributes, attributes) &&
other.ContainingEntityType == ContainingEntityType;
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(CollectionIdMapping)) return false;
return Equals((CollectionIdMapping)obj);
}

public override int GetHashCode()
{
unchecked
{
int result = (attributes is not null ? attributes.GetHashCode() : 0);
result = (result * 397) ^ (ContainingEntityType is not null ? ContainingEntityType.GetHashCode() : 0);
return result;
}
}

public void Set<T>(Expression<Func<CollectionIdMapping, T>> expression, int layer, T value)
{
Set(expression.ToMember().Name, layer, value);
}

protected override void Set(string attribute, int layer, object value)
{
attributes.Set(attribute, layer, value);
}

public override bool IsSpecified(string attribute)
{
return attributes.IsSpecified(attribute);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace FluentNHibernate.MappingModel.Collections;

[Serializable]
public class CollectionMapping : MappingBase, IRelationship, IEquatable<CollectionMapping>
public sealed class CollectionMapping : MappingBase, IRelationship, IEquatable<CollectionMapping>
{
readonly AttributeStore attributes;
readonly List<FilterMapping> filters = [];
Expand All @@ -32,6 +32,9 @@ public override void AcceptVisitor(IMappingModelVisitor visitor)
{
visitor.ProcessCollection(this);

if (CollectionId is not null && Collection == Collection.IdBag)
visitor.Visit(CollectionId);

if (Key is not null)
visitor.Visit(Key);

Expand Down Expand Up @@ -109,6 +112,8 @@ public override void AcceptVisitor(IMappingModelVisitor visitor)
public string Sort => attributes.GetOrDefault<string>();

public IIndexMapping Index => attributes.GetOrDefault<IIndexMapping>();

public CollectionIdMapping CollectionId => attributes.GetOrDefault<CollectionIdMapping>();

public bool Equals(CollectionMapping other)
{
Expand Down Expand Up @@ -174,6 +179,11 @@ public static CollectionMapping Bag(AttributeStore underlyingStore)
{
return For(Collection.Bag, underlyingStore);
}

public static CollectionMapping IdBag(AttributeStore underlyingStore)
{
return For(Collection.IdBag, underlyingStore);
}

public static CollectionMapping List()
{
Expand Down
Loading