2
votes

I'm using Fluent NHibernate's AutoMap feature to map my entities. Most of my entities inherit from a base class Entity which has a property public IList<Tag> Tags.

The tags are in a separate table in the database, so I use a many-to-many relation. But Fluent NHibernate creates mappings for a one-to-many relation.

I'd like to write a convention to override these mappings to use HasManyToMany(...) if the class inherits from Entity. Is this possible and how?

The convention could either rely on the property's type or its name.

Some code for illustration:

// entities
public class Entity
{
    public virtual int Id { get; set; }
    // ... some other properties
    public virtual IList<Tag> { get; set; }
}

public class Tag
{
    public virtual int Id { get; set; }
    public virtual string TagName { get; set; }
}

public class Event : Entity
{
    // ... some properties
}

// Fluent NHibernate configuration
public static ISessionFactory CreateSessionFactory()
{
    var config = new CustomAutomappingConfiguration();
    return Fluently.Configure()
        .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("Sql")))
        .Mappings(m =>
        {
            m.AutoMappings.Add(AutoMap.AssemblyOf<Event>(config)
                .IgnoreBase<Entity>()
                .Conventions.Add<CustomForeignKeyConvention>()
                .Conventions.Add<CustomManyToManyTableNameConvention>();
        })
        .BuildSessionFactory();
}
2
which inheritance strategy do you use? table-per-concrete-class, table-per-classhirarchy or table-per-subclass?Firo
I'm not sure, which one this is exactly. Maybe an example can explain: I have Event : Entity which maps to the table named 'Event' containing all properties of Event and the base class Entity.davehauser
this would be table-per-concrete-class. There i only know of Override<> for each subclass, which is not what you wanted or heavy use of reflection to call override with the dynamically filtered typesFiro
Override<> (or implementations of IAutoMappingOverride<>) for each entity is exactly, what I could find so far :-) It works, but I have to remember, when I add another entity. Unfortunately an override of the mapping for Entity has no effect... Thanks anyway!davehauser

2 Answers

0
votes

I don't think you can accomplish the mapping with conventions. However, if you want to keep one linking table between the entities and tags, you can do the following:

m.AutoMappings.Add(AutoMap.AssemblyOf<Event>(config)
    .IncludeBase<Entity>()    
    .Override<Entity>(map => 
         map.HasManyToMany(e => e.Tags)
            .Inverse()
            .Cascade.SaveUpdate()));

Notice that I changed IgnoreBase<Entity>() to IncludeBase<Entity>(). This will add an Entity table, but will keep one linking table. With this mapping, you will get the following table DDL:

create table [Entity] (
    Id INT IDENTITY NOT NULL,
   primary key (Id)
)

create table TagToEntity (
    Entity_id INT not null,
   Tag_id INT not null
)

create table Event (
    Entity_id INT not null,
   primary key (Entity_id)
)

create table [Tag] (
    Id INT IDENTITY NOT NULL,
   TagName NVARCHAR(255) null,
   primary key (Id)
)

alter table TagToEntity 
    add constraint FKD7554554A8C4CA9 
    foreign key (Tag_id) 
    references [Tag]

alter table TagToEntity 
    add constraint FKD75545564C9EC79 
    foreign key (Entity_id) 
    references [Entity]

alter table Event 
    add constraint FKA2FD7DF664C9EC79 
    foreign key (Entity_id) 
    references [Entity]

If you choose to do an Override<> per subclass, you will have a linking table per subclass.

0
votes

In my case, I wanted to use an attribute to indicate a property that should participate in a many-to-many relationship where only one side of the relationship is declared. You could easily modify this to map by other conventions.

Many-to-many relationships are handled by FluentNHibernate.Automapping.Steps.HasManyToManyStep, an IAutomappingStep returned by the DefaultAutomappingConfiguration. This step will only map a property if it discovers a corresponding property of the related type (so both ends of the many-to-many relationship have to be declared).

The approach I've taken is to:

  • Create a decorator class for HasManyToManyStep that supports detecting and mapping many-to-many properties based on the presence of an attribute (or some other convention)
  • Create a class derived from DefaultAutomappingConfiguration to when automapping and override GetMappingSteps, wrapping any instance of HasManyToManyStep with the decorator

Here's the decorator, which tries to use the default HasManyToManyStep functionality first. Otherwise, if HasManyToManyAttribute is defined for the member, it will also create the relationship. The code used to create the relationship is nearly identical to the code used by HasManyToManyStep - just without reference to the other side of the relationship.

class ExplicitHasManyToManyStep : IAutomappingStep
{
    readonly IAutomappingConfiguration Configuration;
    readonly IAutomappingStep DefaultManyToManyStep;

    public ExplicitHasManyToManyStep(IAutomappingConfiguration configuration, IAutomappingStep defaultManyToManyStep)
    {
        Configuration = configuration;
        DefaultManyToManyStep = defaultManyToManyStep;
    }

    #region Implementation of IAutomappingStep

    public bool ShouldMap(Member member)
    {
        if (DefaultManyToManyStep.ShouldMap(member))
        {
            return true;
        }

        //modify this statement to check for other attributes or conventions
        return member.MemberInfo.IsDefined(typeof(HasManyToManyAttribute), true);
    }

    public void Map(ClassMappingBase classMap, Member member)
    {
        if (DefaultManyToManyStep.ShouldMap(member))
        {
            DefaultManyToManyStep.Map(classMap, member);
            return;
        }

        var Collection = CreateManyToMany(classMap, member);
        classMap.AddCollection(Collection);
    }

    #endregion

    CollectionMapping CreateManyToMany(ClassMappingBase classMap, Member member)
    {
        var ParentType = classMap.Type;
        var ChildType = member.PropertyType.GetGenericArguments()[0];

        var Collection = CollectionMapping.For(CollectionTypeResolver.Resolve(member));
        Collection.ContainingEntityType = ParentType;
        Collection.Set(x => x.Name, Layer.Defaults, member.Name);
        Collection.Set(x => x.Relationship, Layer.Defaults, CreateManyToMany(member, ParentType, ChildType));
        Collection.Set(x => x.ChildType, Layer.Defaults, ChildType);
        Collection.Member = member;

        SetDefaultAccess(member, Collection);
        SetKey(member, classMap, Collection);
        return Collection;
    }

    void SetDefaultAccess(Member member, CollectionMapping mapping)
    {
        var ResolvedAccess = MemberAccessResolver.Resolve(member);

        if (ResolvedAccess != Access.Property && ResolvedAccess != Access.Unset)
        {
            mapping.Set(x => x.Access, Layer.Defaults, ResolvedAccess.ToString());
        }

        if (member.IsProperty && !member.CanWrite)
        {
            mapping.Set(x => x.Access, Layer.Defaults, Configuration.GetAccessStrategyForReadOnlyProperty(member).ToString());
        }
    }

    static ICollectionRelationshipMapping CreateManyToMany(Member member, Type parentType, Type childType)
    {
        var ColumnMapping = new ColumnMapping();
        ColumnMapping.Set(x => x.Name, Layer.Defaults, childType.Name + "_id");

        var Mapping = new ManyToManyMapping {ContainingEntityType = parentType};
        Mapping.Set(x => x.Class, Layer.Defaults, new FluentNHibernate.MappingModel.TypeReference(childType));
        Mapping.Set(x => x.ParentType, Layer.Defaults, parentType);
        Mapping.Set(x => x.ChildType, Layer.Defaults, childType);
        Mapping.AddColumn(Layer.Defaults, ColumnMapping);

        return Mapping;
    }

    static void SetKey(Member property, ClassMappingBase classMap, CollectionMapping mapping)
    {
        var ColumnName = property.DeclaringType.Name + "_id";
        var ColumnMapping = new ColumnMapping();
        ColumnMapping.Set(x => x.Name, Layer.Defaults, ColumnName);

        var Key = new KeyMapping {ContainingEntityType = classMap.Type};
        Key.AddColumn(Layer.Defaults, ColumnMapping);

        mapping.Set(x => x.Key, Layer.Defaults, Key);
    }
}

HasManyToManyAttribute class, because there is no other convention I can easily rely on in my case:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class HasManyToManyAttribute : Attribute
{
}

Configuration class derived from DefaultMappingConfiguration class:

class AutomappingConfiguration : DefaultAutomappingConfiguration
{
    public override IEnumerable<IAutomappingStep> GetMappingSteps(AutoMapper mapper, IConventionFinder conventionFinder)
    {
        return base.GetMappingSteps(mapper, conventionFinder).Select(GetDecoratedStep);
    }

    IAutomappingStep GetDecoratedStep(IAutomappingStep step)
    {
        if (step is HasManyToManyStep)
        {
            return new ExplicitHasManyToManyStep(this, step);
        }

        return step;
    }
}