Fluent NHibernate:按照约定映射 HasManyToMany

发布于 2024-11-26 02:50:30 字数 1329 浏览 3 评论 0原文

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

标签位于数据库中的单独表中,因此我使用多对多关系。但是 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();
}

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

幸福丶如此 2024-12-03 02:50:30

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

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

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

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.

你曾走过我的故事 2024-12-03 02:50:30

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

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

我采取的方法是:

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

这是装饰器,它尝试使用默认的首先具有ManyToManyStep 功能。否则,如果为成员定义了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;
    }
}

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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文