2015-05-18 7 views
15

DESIGN TŁO:EF 6 - Kodeks pierwszy nieważne jeden-do-jednego relacji klucza obcego

Próbuję utworzyć kod pierwszego mapowania EF6 o następującej strukturze bazy danych:

projekt bazy danych jest następujące: Zamiast "ID klienta" jako klucza obcego we wszystkich powiązanych jednostkach (Zatrudnienie, wydatki, dochody itp.), mamy tabelę CustomerRelationship, która będzie zawierała identyfikator klienta, a następnie kolumnę "RelatedID", która będzie zawierał klucz powiązanej jednostki. Na przykład, powiedzmy, że mogę dodać rekord zatrudnienia dla CustomerID = 1, to stanie się co następuje:

  1. Tworzenie rekordu w CustomerRelationship ustawienie CustomerID = 1 RelatedID = {nowy wygenerowany automatycznie EmploymentID, powiedzmy, 5} CustomerRelationshipTypeID = 55 (Id w tabeli przeglądowej, która stwierdza, że ​​ ten rekord jest typu zatrudnienia)

  2. Tworzenie rekordu w tabeli zatrudnienia (EmploymentID = 5)

Powyższa struktura będzie działać dla wszystkich podmiotów powiązanych z Klientem.

DB Diagram mam mapowania współpracę na rzecz zatrudnienia, oto moje klasy:

public abstract class EntityBase : IEntity 
{ 
    #region IEntity Members 
    public int Id { get; set; } 

    public DateTime CreatedDate { get; set; } 

    public int CreatedUserId { get; set; } 

    public int CreatedSource { get; set; } 

    public DateTime ModifiedDate { get; set; } 

    public int ModifiedUserId { get; set; } 

    public int? DataMigrationId { get; set; } 

    public bool IsActive { get; set; }          
    #endregion 
} 


public class Employment : EntityBase 
{ 
    // ... all properties here.. removed most so easier to read 
    public int EmploymentTypeId { get; set; }  

    **public virtual ICollection<EmploymentRelationship> EmploymentRelationships { get; set; }** 
} 

    public EmploymentMap() 
    { 
     this.HasKey(t => t.Id); 
     ToTable("tblEmployment"); 
     Property(t => t.Id).HasColumnName("EmploymentID");  
     // Mapping for all properties follow  
    } 

public abstract partial class CustomerRelationship : EntityBase 
{ 
    public int CustomerId { get; set; } 

    public decimal? PercentageShare { get; set; } 

    public int CustomerRelationshipTypeId { get; set; } 

    public int RelatedId { get; set; } 
} 

public class EmploymentRelationship : CustomerRelationship 
{  
    public virtual Employment Employment { get; set; } 
} 

    public EmploymentRelationshipMap() 
    { 
     this.HasKey(t => t.Id); 

     Map<EmploymentRelationship>(m => 
     { 
      m.Requires("CustomerRelationshipTypeID").HasValue(55).IsRequired(); // Define lookup value for type of employment 
      m.ToTable("tblCustomerRelationship"); 
     }); 

     Property(t => t.Id).HasColumnName("CustomerRelationshipID"); 
     Property(t => t.CustomerId).HasColumnName("CustomerID"); 
     Property(t => t.RelatedId).HasColumnName("RelatedID"); 

     HasRequired(t => t.Employment) 
      .WithMany(t => t.EmploymentRelationships) 
      .HasForeignKey(t => t.RelatedId); 
    } 

public class Customer : EntityBase 
{ 
    // Customer Properties... 
    public Customer() 
    { 
     EmploymentRelationships = new List<EmploymentRelationship>(); 
    } 

    public virtual ICollection<EmploymentRelationship> EmploymentRelationships { get; set; } 
} 

    public CustomerMap() 
    { 
     this.HasKey(t => t.Id); 

     ToTable("tblCustomer"); 

     Property(t => t.Id).HasColumnName("CustomerID"); 
    } 


public class CustomerContext 
{ 
    public CustomerContext() 
     : base(SymmetryCopy.context_connectionstring_main) 
    { 
    } 

    public virtual DbSet<Customer> Customers { get; set; } 
    public virtual DbSet<Employment> Employments { get; set; } 

    #region Customer Relationship entity mappings 
    public virtual DbSet<EmploymentRelationship> EmploymentRelationships { get; set; } 
    #endregion 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Configurations.Add(new CustomerMap()); 
     modelBuilder.Configurations.Add(new EmploymentMap()); 

     #region Customer Relationship entity mappings 
     modelBuilder.Configurations.Add(new EmploymentRelationshipMap()); 
     #endregion 
    } 
} 

CustomerRepo do kontekstu kwerend i powrotu wyników:

public class CustomerRepository : BaseRepository<Customer, CustomerContext>, ICustomerRepository 
{ 
    public CustomerRepository() : 
     base(new CustomerContext()) 
    { 

    } 

    public async Task<List<Employment>> GetEmployments(int customerId) 
    { 
     List<Employment> employments = new List<Employment>(); 
     using (var context = new CustomerContext()) 
     { 
      var employmentRelationships = context.EmploymentRelationships.Where(l => l.CustomerId == customerId).ToList(); 
      employments = employmentRelationships.Select(x => x.Employment).ToList(); 
     } 
     return employments; 
    } 
} 

Powyższa metoda GetEmployments następnie zwraca wszystkie rekordy identyfikujące CustomerID z CustomerRelingshipTypeID = 5 5 (Wartość kluczowa dla zatrudnienia). Zobacz powroty poniżej.

enter image description here

teraz, aby dostać się do moich rzeczywistych pytań:

Kiedy spróbować podłączyć inny podmiot typu, a mianowicie: koszt, zgodnie z tą samą podejścia jako że od zatrudnienia, tworzenie Expense.cs, ExpenseMap .cs, ExpenseRelationship.cs, ExpenseRelationshipMap.cs, posiadające następujące w ExpenseRElationshipMap.cs:

public class ExpenseRelationshipMap 
{ 
    public ExpenseRelationshipMap() 
    { 
     HasKey(t => t.Id); 

     Map<ExpenseRelationship>(m => 
     { 
      m.Requires("CustomerRelationshipTypeID").HasValue(60).IsRequired(); 
      m.ToTable("tblCustomerRelationship"); // Define lookup value for type of Expense 
     }); 

     Property(t => t.Id).HasColumnName("CustomerRelationshipID"); 
     Property(t => t.CustomerId).HasColumnName("CustomerID"); 
     Property(t => t.RelatedId).HasColumnName("RelatedID"); 
     Property(t => t.PercentageShare).HasColumnName("PercentageShare"); 

     HasRequired(t => t.Expense) 
      .WithMany(t => t.ExpenseRelationships) 
      .HasForeignKey(t => t.RelatedId); 
    } 
} 

Raz Utworzono wpis Mapa, jak wykazano powyżej, gdy quering z GetEmployments() metodę, teraz pojawia się następujący exc eption:

„typów jednostki ExpenseRelationship 'i«EmploymentRelationship» nie mogą dzielić stół«tblCustomerRelationship», ponieważ nie są one w tej samej hierarchii typu lub nie posiada ważnej jeden do jednego klucza obcego relacji z pasujące klucze podstawowe między nimi. ",

Czego mi brakuje?

UPDATE

Zgodnie komentarzach jjj, I zostały zaktualizowane moje mapowania i stworzył klasę bazową CustomerRelationship.cs.

public class Employment : EntityBase 
{  
    public string EmployerName { get; set; } 

    public string EmployerContactFirstName { get; set; } 

    public string EmployerContactSurname { get; set; } 

    public virtual ICollection<EmploymentRelationship> EmploymentRelationships { get; set; } 
} 

public class Expense : EntityBase 
{ 
    public string Description { get; set; } 

    public virtual ICollection<ExpenseRelationship> ExpenseRelationships { get; set; } 
} 

public abstract class CustomerRelationship : EntityBase 
{ 
    public int CustomerId { get; set; } 

    public int? CustomerRelationshipTypeId { get; set; } 

    public int RelatedId { get; set; } 
} 

public class EmploymentRelationship : CustomerRelationship 
{ 
    public virtual Employment Employment { get; set; } 
} 

public class ExpenseRelationship: CustomerRelationship 
{ 
    public virtual Expense Expense{ get; set; } 
} 

public class CustomerRelationshipMap : BaseMap<CustomerRelationship> 
{ 
    public CustomerRelationshipMap() 
    { 
     ToTable("CustomerRelationship"); 

     Map<EmploymentRelationship>(m => m.Requires("CustomerRelationshipTypeID").HasValue(55)); 
     Map<ExpenseRelationship>(m => m.Requires("CustomerRelationshipTypeID").HasValue(60)); 

     Property(t => t.Id).HasColumnName("CustomerRelationshipID");    
     Property(t => t.CustomerId).HasColumnName("CustomerID"); 
     Property(t => t.RelatedId).HasColumnName("RelatedID");    
    } 

public class EmploymentRelationshipMap : BaseMap<EmploymentRelationship> 
{ 
    public EmploymentRelationshipMap() 
    { 
     HasRequired(t => t.Employment) 
      .WithMany(t => t.EmploymentRelationships) 
      .HasForeignKey(t => t.RelatedId); 
    } 
} 

public class ExpenseRelationshipMap : BaseMap<ExpenseRelationship> 
{ 
    public ExpenseRelationshipMap() 
    { 
     HasRequired(t => t.Expense) 
      .WithMany(t => t.ExpenseRelationships) 
      .HasForeignKey(t => t.RelatedId); 
    } 
} 

public class CustomerContext : BaseContext 
{ 
    public CustomerContext() 
     : base(context_connectionstring_main) 
    { 
    } 

    public virtual DbSet<Customer> Customers { get; set; } 
    public virtual DbSet<Employment> Employments { get; set; } 

    public virtual DbSet<CustomerRelationship> CustomerRelationships { get; set; } 
    public virtual DbSet<EmploymentRelationship> EmploymentRelationships { get; set; } 
    public virtual DbSet<ExpenseRelationship> ExpenseRelationships { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Configurations.Add(new CustomerMap()); 
     modelBuilder.Configurations.Add(new EmploymentMap()); 

     modelBuilder.Configurations.Add(new CustomerRelationshipMap()); 
     modelBuilder.Configurations.Add(new EmploymentRelationshipMap()); 
     modelBuilder.Configurations.Add(new ExpenseRelationshipMap()); 
    } 
} 

Podczas kwerendy kontekst klienta tak:

var relationships = context.CustomerRelationships.Where(l => l.CustomerId == customerId).ToList(); 

otrzymuję następujący wyjątek:

„Komponent klucza obcego 'RelatedId' nie jest zadeklarowana nieruchomość na wpisz "EmploymentRelationship". Sprawdź, czy nie został jawnie wyłączony z modelu i czy jest to prawidłowa prymitywna właściwość. ",

+1

Ok, to błąd ze składnikiem klucza obcego ma sens. Myślę, że nie można użyć właściwości klasy podstawowej dla powiązania klucza obcego klasy pochodnej. Zobacz http://stackoverflow.com/questions/11900155/how-to-map-foreign-keys-between-tph-tpt-objects-entity-framework-code-first i http://stackoverflow.com/questions/25619452/entity-framework-fluent-api-does-not-consider-base-class-properties – jjj

+0

Używasz relacyjnej bazy danych. Dlaczego nie chcesz modelować relacji za pomocą relacji? – Colin

Odpowiedz

7

Potrzebna jest konfiguracja klasy podstawowej dla wszystkich wspólnych właściwości (w tym klucza podstawowego).

public class CustomerRelationshipMap : EntityTypeConfiguration<CustomerRelationship> 
{ 
    public CustomerRelationshipMap() 
    { 
     ToTable("tblCustomerRelationship"); 

     Map<EmploymentRelationship>(m => m.Requires("CustomerRelationshipTypeID").HasValue(55)); 
     Map<ExpenseRelationship>(m => m.Requires("CustomerRelationshipTypeID").HasValue(60)); 

     HasKey(t => t.Id); 
     Property(t => t.Id).HasColumnName("CustomerRelationshipID"); 
     Property(t => t.CustomerId).HasColumnName("CustomerID"); 
     Property(t => t.RelatedId).HasColumnName("RelatedID"); 
    } 
} 

Następnie powinieneś mieć możliwość konfiguracji pochodzi klasy specyficzne w innych klasach konfiguracyjnych (choć nie jest to coś próbowałem wcześniej).

Edit

Ponadto, nie można mieć różne skojarzenia kluczy obcych dla klas pochodnych wykorzystujących tę samą właściwość klasy bazowej. Istnieje kilka opcji, mogę myśleć, ale to zależy od sytuacji:

  1. Oddzielne klucze obce dla związku między EmploymentRelationship - Employment i ExpenseRelationship - Expense.
  2. Nadanie Employment i Expense wspólny klasy bazowej, a także - mimo, że może pokonać cel, co próbujesz zrobić ....
  3. Oddzielne 1: 0..1 relacje między CustomerRelationship i Employment/Expense (i pozbycie EmploymentRelationship i ExpenseRelationship)
  4. TPT spadkowego gdzie Employment i Expense dziedziczą z CustomerRelationship (i pozbycie EmploymentRelationship i ExpenseRelationship)

Źródła

+0

@FaNIX: Przepraszam, dostałem ostatnią część źle. : -/Dokonałem edycji. – jjj

+0

Co rozumiesz przez "oddzielne klucze zagraniczne do powiązania z zatrudnieniem i wydatkami" – FaNIX

+0

@FaNIX: Myślałem o oddzielnych kolumnach dla EmploymentId i ExpenseId w CustomerRelationship. Prawdopodobnie nie jest to dobre rozwiązanie – jjj