Fluent NHibernate: Mapping HasManyToMany by convention

江枫思渺然 提交于 2020-01-02 10:15:46

问题


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();
}

回答1:


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.




回答2:


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;
    }
}


来源:https://stackoverflow.com/questions/6822430/fluent-nhibernate-mapping-hasmanytomany-by-convention

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!