流畅的NHibernate:按惯例映射HasManyToMany

时间:2011-07-25 21:12:10

标签: fluent-nhibernate many-to-many conventions automapping

我正在使用Fluent NHibernate的AutoMap功能来映射我的实体。我的大多数实体都从基类Entity继承,它具有属性public IList<Tag> Tags

标签位于数据库的单独表中,因此我使用多对多关系。但是Fluent NHibernate为一对多关系创建了映射。

如果类继承自HasManyToMany(...),我想编写一个约定来覆盖这些映射以使用Entity。这可能吗?如何?

约定可以依赖于属性的类型或名称。

一些代码示例:

// 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 个答案:

答案 0 :(得分:0)

我认为你不能用惯例完成映射。但是,如果要在实体和标记之间保留一个链接表,则可以执行以下操作:

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

请注意,我已将IgnoreBase<Entity>()更改为IncludeBase<Entity>()。这将添加一个Entity表,但会保留一个链接表。通过此映射,您将获得下表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]

如果您选择为每个子类执行Override<>,则每个子类将有一个链接表。

答案 1 :(得分:0)

就我而言,我想使用一个属性来表示一个属性,该属性应该参与多对多关系,其中只声明关系的一侧。您可以轻松地将其修改为按其他约定进行映射。

多对多关系由FluentNHibernate.Automapping.Steps.HasManyToManyStep处理,IAutomappingStep返回DefaultAutomappingConfiguration。如果属性发现相关类型的相应属性,则此步骤将仅映射属性(因此必须声明多对多关系的两端)。

我采取的方法是:

  • HasManyToManyStep创建一个装饰器类,支持根据属性(或其他约定)的存在检测和映射多对多属性
  • 创建一个派生自DefaultAutomappingConfiguration的类到automapping并覆盖GetMappingSteps,用装饰器包装HasManyToManyStep的任何实例

这是装饰器,它首先尝试使用默认的HasManyToManyStep功能。否则,如果为成员定义了HasManyToManyAttribute,它也将创建关系。用于创建关系的代码几乎与HasManyToManyStep使用的代码相同 - 只是没有引用关系的另一面。

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课程,因为在我的案例中没有其他惯例我可以轻易依赖:

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

DefaultMappingConfiguration类派生的配置类:

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