【问题标题】:Fluent NHibernate: Mapping HasManyToMany by conventionFluent NHibernate:按约定映射 HasManyToMany
【发布时间】:2012-08-30 22:46:06
【问题描述】:

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

标签在数据库中的一个单独的表中,所以我使用多对多关系。但 Fluent NHibernate 为一对多关系创建映射。

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

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

一些代码用于说明:

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

【问题讨论】:

  • 您使用哪种继承策略?每个具体类的表、每个类的表层次结构或每个子类的表?
  • 我不确定,这到底是哪一个。也许一个例子可以解释:我有Event : Entity,它映射到名为“事件”的表,其中包含Event的所有属性和基类Entity
  • 这将是每个具体类的表。我只知道每个子类的Override&lt;&gt;,这不是您想要的或大量使用反射来调用动态过滤类型的覆盖
  • Override&lt;&gt; (或IAutoMappingOverride&lt;&gt; 的实现)对于每个实体都是准确的,到目前为止我能找到:-) 它有效,但我必须记住,当我添加另一个实体时。不幸的是,覆盖 Entity 的映射没有效果......无论如何,谢谢!

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


【解决方案1】:

我认为您无法通过约定来完成映射。但是,如果您想在实体和标签之间保留一个链接表,您可以执行以下操作:

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

请注意,我将 IgnoreBase&lt;Entity&gt;() 更改为 IncludeBase&lt;Entity&gt;()。这将添加一个实体表,但将保留一个链接表。通过此映射,您将获得下表 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&lt;&gt;,您将拥有每个子类的链接表。

【讨论】:

  • 感谢您的建议。我有一个现有的数据库,我希望我的实体映射到该数据库。我有每个子类的链接表。至于每个子类的Override&lt;&gt;,这正是我试图避免的:-)
  • 对不起...我想你是想避免这种情况,但我想我还是会发帖的。尝试将我的解决方案与现有数据库一起使用会导致身份问题。尝试使用约定进行映射是行不通的,因为 FNH 无法确定关系 - 标签需要实体集合(这将失败 b/c 实体未映射)。我会再想一想,看看能不能想出别的办法。
  • 所以你说,我想做的事情是不可能的?真可惜! :-) 没有约定说“如果属性是 IList 类型而不是使用多对多关系”?...可能
  • 就像我说的,还在寻找。我已经做了很多基于约定的映射,这个让我很困惑。 >8(仍在尝试。
【解决方案2】:

在我的例子中,我想使用一个属性来指示一个应该参与多对多关系的属性,其中只声明了关系的一侧。您可以轻松地修改它以按其他约定映射。

多对多关系由FluentNHibernate.Automapping.Steps.HasManyToManyStep 处理,IAutomappingStepDefaultAutomappingConfiguration 返回。此步骤仅在发现相关类型的对应属性时才会映射属性(因此必须声明多对多关系的两端)。

我采取的方法是:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-08
    • 1970-01-01
    • 2010-11-22
    • 1970-01-01
    • 2011-04-10
    • 1970-01-01
    • 2011-06-18
    相关资源
    最近更新 更多