Свободное владение NHibernate: отображение HasManyToMany по соглашению - PullRequest
2 голосов
/ 26 июля 2011

Я использую функцию AutoMap Fluent NHibernate для сопоставления своих сущностей.Большинство моих сущностей наследуются от базового класса 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 голосов
/ 30 августа 2012

В моем случае я хотел использовать атрибут, чтобы указать свойство, которое должно участвовать в отношении «многие ко многим», где объявлена ​​только одна сторона отношения. Вы можете легко изменить это, чтобы сопоставить с другими соглашениями.

Отношения «многие ко многим» обрабатываются FluentNHibernate.Automapping.Steps.HasManyToManyStep, IAutomappingStep возвращается DefaultAutomappingConfiguration. Этот шаг отобразит свойство только в том случае, если он обнаружит соответствующее свойство связанного типа (поэтому необходимо объявить оба конца отношения «многие ко многим»).

Я выбрал следующий подход:

  • Создание класса декоратора для HasManyToManyStep, который поддерживает обнаружение и отображение свойств многие-ко-многим на основе наличия атрибута (или какого-либо другого соглашения)
  • Создайте класс, производный от DefaultAutomappingConfiguration до того момента, когда автонастроит и переопределит 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:

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;
    }
}
0 голосов
/ 26 июля 2011

Я не думаю, что вы можете выполнить сопоставление с соглашениями. Однако, если вы хотите сохранить одну таблицу связывания между сущностями и тегами, вы можете сделать следующее:

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

Обратите внимание, что я изменил IgnoreBase<Entity>() на IncludeBase<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<> для подкласса, у вас будет таблица связывания для подкласса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...