Определите отношения для иерархических данных в Entity Framework Code First - PullRequest
2 голосов
/ 29 октября 2011

У меня есть иерархическая связь, определенная для одной из моих таблиц, где эта связь хранится в отдельной таблице соединений.Таблица соединений также содержит информацию о типе отношений.(Упрощенная) схема выглядит следующим образом:

Bills
   ID int IDENTITY(1,1) NOT NULL (PK)
   Code varchar(5) NOT NULL
   Number varchar(5) NOT NULL
   ...

BillRelations
   ID int IDENTITY(1,1) NOT NULL (PK)
   BillID int NOT NULL
   RelatedBillID int NOT NULL
   Relationship int NOT NULL
   ...

У меня есть отношения FK, определенные в BillID и RelatedBillID к идентификатору в таблице Bills.

Я пытаюсь отобразить это в Entity Framework CodeСначала с небольшим успехом.Мои занятия выглядят следующим образом.Не обращайте внимания на RelationshipWrapper, это класс-оболочка для enum с именем Relationship, который соответствует значению в столбце Отношения.

public class Bill
{
     [Key]
     [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
     public int ID { get; set; }

     [Required]
     [StringLength(5)]
     public string Code { get; set; }

     [Required]
     [StringLength(5)]
     public string Number { get; set; }

     public virtual ICollection<BillRelation> RelatedBills { get; set; }

     ...
}

public class BillRelation
{
     [Key]
     [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
     public int ID { get; set; }

     public long BillID { get; set; }

     [ForeignKey("BillID")]
     public virtual Bill Bill { get; set; }

     public long RelatedBillID { get; set; }

     [ForeignKey("RelatedBillID")]
     public virtual Bill RelatedBill { get; set; }

     public RelationshipWrapper Relationship { get; set; }

     ...
 }

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

Используя эту настройку, я получаю ошибку: Bill_ID column is not defined (или что-то подобное).

Если я получилМаршрут ModelBuilder с использованием следующего, я получаю сообщение об ошибке «Таблица BillRelations не существует в базе данных».

modelBuilder.Entity<Bill>().HasMany( b => b.RelatedBills )
                           .WithRequired( r => r.Bill )
                           .Map( m => m.MapKey( "BillID" ).ToTable( "BillRelations" ) );
modelBuilder.Entity<BillRelation>().HasRequired( r => r.RelatedBill )
                                   .WithRequiredDependent()
                                   .Map( m => m.MapKey( "RemoteBillID" ).ToTable( "BillRelations" ) );

Мне удалось заставить его работать, определив только одну половину отношения,Счета -> BillRelations, затем с помощью Join в моем репозитории заполнить свойство [NotMapped] RelatedBill класса BillRelations для каждого из связанных счетов в коллекции RelatedBills счета.Я бы предпочел не делать этого, если смогу помочь.

Единственное другое решение, о котором я подумал, - это смоделировать каждое отношение в отдельной таблице (существует 4 типа) и использовать стандартный Билл <-> Билл отображает таблицу соединений для каждого из 4 типов отношений - опять же, я бы предпочел не делать этого, если смогу избежать этого.

Может кто-нибудь увидеть, что я делаю неправильно, или сказать мне, если чтоЯ хочу сделать это даже возможно в EF Code First 4.1?

1 Ответ

2 голосов
/ 29 октября 2011

Всего несколько идей:

  • Ваше отображение с аннотациями данных не работает, поскольку соглашения EF Code First не распознают, какие свойства навигации принадлежат друг другу.Очевидно, вы хотите связать Bill.RelatedBills с BillRelation.Bill.Но поскольку есть второе свойство навигации BillRelation.RelatedBill, относящееся также к сущности Bill, AssociationInverseDiscoveryConvention не может быть применено для распознавания правильного отношения.Это соглашение работает, только если у вас есть ровно одна пара свойств навигации на объектах.Как следствие, EF предполагает фактически три отношения, каждое из которых имеет только один открытый конец в модели.Отношение, к которому принадлежит Bill.RelatedBills, предполагает наличие незащищенного внешнего ключа на другой стороне в соответствии с соглашениями об именах EF по умолчанию, что составляет Bill_ID с подчеркиванием.Его нет в базе данных, следовательно, исключение.

  • В вашем сопоставлении Fluent API я бы просто попытался удалить ...ToTable(...) в целом.Я считаю, что в этом нет необходимости, поскольку отображение в любом случае знает, к какой таблице принадлежит внешний ключ.Возможно, это исправит вторую ошибку («Таблица ... не существует ....»)

  • Ваше второе отображение - отношение один к одному - возможно, не работаеткак и ожидалось, потому что отношения «один-к-одному» «обычно» сопоставляются с ассоциацией общего первичного ключа между таблицами в базе данных.(Однако я не уверен, что общие первичные ключи действительно требуются EF.) Поскольку ваш столбец BillId выглядит как внешний ключ, который в то же время не является первичным ключом, я бы попытался сопоставить отношение как однозначный.-много.Более того, поскольку столбцы внешнего ключа представлены в качестве свойств в модели, вы должны использовать HasForeignKey вместо MapKey:

    modelBuilder.Entity<Bill>()
        .HasMany( b => b.RelatedBills )
        .WithRequired( r => r.Bill )
        .HasForeignKey ( r => r.BillID );
    // turn off/on cascade delete by chaining
    //  .WillCascadeOnDelete(true/false)
    // here at the end
    
    modelBuilder.Entity<BillRelation>()
        .HasRequired( r => r.RelatedBill )
        .WithMany()
        .HasForeignKey ( r => r.RelatedBillID );
    // turn off/on cascade delete by chaining
    //  .WillCascadeOnDelete(true/false)
    // here at the end
    

Edit

Возможно, что полное отображение Fluent не требуется, если вы поместите атрибут [InverseProperty] в одно из свойств навигации - например, в свой класс Bill:

[InverseProperty("Bill")]
public virtual ICollection<BillRelation> RelatedBills { get; set; }

В этом атрибутеВы указываете имя связанного свойства навигации в связанном объекте.Это связывает Bill.RelatedBills и BillRelation.Bill вместе с парой навигационных свойств, являющихся концами одной и той же ассоциации.Я надеюсь, что EF сделает правильно с оставшимся свойством навигации BillRelation.RelatedBill, то есть создаст отношение «один ко многим» - я надеюсь ... Если каскадное удаление по умолчанию не сработает, вы вынуждены использоватьСвободный API, хотя, поскольку отсутствует атрибут аннотации данных для настройки каскадного удаления.

...