2016-06-13 69 views
1

在數據庫設計中,通常的做法是在「一個」(或子)側使用外鍵表示一對多關係。實體框架很好地處理了這種情況。通過連接表在實體框架中快速建立一對多關係

但是,我有這樣的情況,一對多關係通過連接表來表示,其中表中的兩個外鍵之一具有唯一約束。

如何配置實體框架來利用這個連接表?

在我當前的狀態下,當對一個/子實體做一個簡單的讀取查詢時,實體框架拋出一個異常---如預期的那樣---一個/子表缺少一個傳統名稱的列基於導航屬性。

+0

如果你有一個連接表,然後..... (即使有db約束來保證1:N),您也必須將EF部分視爲M:N。您可以在EF-Entity上編寫一個(僅獲取)屬性,該屬性將帶回FirstOrDefault(),該屬性將在1:N中帶回您的「one」。 – granadaCoder

+0

@granadaCoder Oo,我喜歡那樣。我會嘗試並報告回來。 –

+0

@granadaCoder但它應該是一個只能得到的屬性。它應該有一個將其添加到集合中的setter。 –

回答

1

如果我有下面的DDL。

員工(M:N)停車場。但是,約束只保留鏈接表中的一個員工,因此是1:N。

- START TSQL

Use OrganizationReverseDB 
GO 


SET NOCOUNT ON 


IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[EmployeeParkingAreaLink]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) 
BEGIN DROP TABLE [dbo].[EmployeeParkingAreaLink] 
END 
GO 

IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[ParkingArea]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) 
BEGIN 
DROP TABLE [dbo].[ParkingArea] 
END 
GO 

IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[Employee]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) 
BEGIN 
DROP TABLE [dbo].[Employee] 
END 


IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[Employee]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) 
BEGIN DROP TABLE [dbo].[Employee] 
END 


IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[Department]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) 
BEGIN DROP TABLE [dbo].[Department] 
END 

GO 


CREATE TABLE [dbo].[Department](
    [DepartmentUUID] [uniqueidentifier] NOT NULL, 
    [TheVersionProperty] [timestamp] NOT NULL, 
    [DepartmentName] [nvarchar](80) NULL, 
    [CreateDate] [datetime] NOT NULL, 
    ) 


ALTER TABLE dbo.[Department] ADD CONSTRAINT PK_Department PRIMARY KEY NONCLUSTERED ([DepartmentUUID]) 
GO 

ALTER TABLE [dbo].[Department] ADD CONSTRAINT CK_DepartmentName_Unique UNIQUE ([DepartmentName]) 
GO 


CREATE TABLE [dbo].[Employee] ( 

    [EmployeeUUID] [uniqueidentifier] NOT NULL, 
    [ParentDepartmentUUID] [uniqueidentifier] NOT NULL, 
    [TheVersionProperty] [timestamp] NOT NULL, 
    [SSN] [nvarchar](11) NOT NULL, 
    [LastName] [varchar](64) NOT NULL, 
    [FirstName] [varchar](64) NOT NULL, 
    [CreateDate] [datetime] NOT NULL, 
    [HireDate] [datetime] NOT NULL 
    ) 

GO 

ALTER TABLE dbo.Employee ADD CONSTRAINT PK_Employee PRIMARY KEY NONCLUSTERED (EmployeeUUID) 
GO 


ALTER TABLE [dbo].[Employee] ADD CONSTRAINT CK_SSN_Unique UNIQUE (SSN) 
GO 

ALTER TABLE [dbo].[Employee] ADD CONSTRAINT FK_EmployeeToDepartment FOREIGN KEY (ParentDepartmentUUID) REFERENCES dbo.Department (DepartmentUUID) 
GO 





IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[ParkingArea]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) 
BEGIN DROP TABLE [dbo].[ParkingArea] 
END 
GO 

CREATE TABLE [dbo].[ParkingArea] 
( 
ParkingAreaUUID [UNIQUEIDENTIFIER] NOT NULL DEFAULT NEWSEQUENTIALID() 
, ParkingAreaName varchar(24) not null 
, CreateDate smalldatetime not null 
) 

GO 

ALTER TABLE dbo.ParkingArea ADD CONSTRAINT PK_ParkingArea PRIMARY KEY NONCLUSTERED (ParkingAreaUUID) 
GO 

ALTER TABLE [dbo].[ParkingArea] ADD CONSTRAINT CK_ParkingAreaName_Unique UNIQUE (ParkingAreaName) 
GO 



IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[EmployeeParkingAreaLink]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) 
BEGIN DROP TABLE [dbo].[EmployeeParkingAreaLink] 
END 
GO 

CREATE TABLE [dbo].[EmployeeParkingAreaLink] ( 
    /* [LinkSurrogateUUID] [uniqueidentifier] NOT NULL, */ 
    [TheEmployeeUUID] [uniqueidentifier] NOT NULL, 
    [TheParkingAreaUUID] [uniqueidentifier] NOT NULL 
) 


GO 

/* 
ALTER TABLE dbo.EmployeeParkingAreaLink ADD CONSTRAINT PK_EmployeeParkingAreaLink PRIMARY KEY NONCLUSTERED (LinkSurrogateUUID) 
*/ 
GO 

ALTER TABLE [dbo].[EmployeeParkingAreaLink] ADD CONSTRAINT FK_EmployeeParkingAreaLinkToEmployee FOREIGN KEY (TheEmployeeUUID) REFERENCES dbo.Employee (EmployeeUUID) 
GO 

ALTER TABLE [dbo].[EmployeeParkingAreaLink] ADD CONSTRAINT FK_EmployeeParkingAreaLinkToParkingArea FOREIGN KEY (TheParkingAreaUUID) REFERENCES dbo.ParkingArea (ParkingAreaUUID) 
GO 

ALTER TABLE [dbo].[EmployeeParkingAreaLink] ADD CONSTRAINT CONST_UNIQUE_EmpUUID UNIQUE (TheEmployeeUUID) 
GO 

ALTER TABLE [dbo].[EmployeeParkingAreaLink] ADD CONSTRAINT CONST_UNIQUE_EmpUUID_PAUUID UNIQUE (TheEmployeeUUID , TheParkingAreaUUID) 
GO 


Insert into dbo.Department ([DepartmentUUID], [DepartmentName] , CreateDate) 
    select '10000000-0000-0000-0000-000000000001' , 'DepartmentOne' , CURRENT_TIMESTAMP 
    union all select '10000000-0000-0000-0000-000000000002' , 'DepartmentTwo' , CURRENT_TIMESTAMP 

Insert into dbo.Employee (EmployeeUUID, SSN , CreateDate, HireDate , LastName, FirstName , ParentDepartmentUUID) 
    select '20000000-0000-0000-0000-000000000001' , '111-11-1111' , CURRENT_TIMESTAMP , '01/31/2001' , 'Smith' , 'John' , '10000000-0000-0000-0000-000000000001' 
    union all select '20000000-0000-0000-0000-000000000002' , '222-22-2222' , CURRENT_TIMESTAMP, '02/28/2002' , 'Jones' , 'Mary' , '10000000-0000-0000-0000-000000000002' 

Insert into dbo.ParkingArea ([ParkingAreaUUID], [ParkingAreaName] , CreateDate) 
    select '30000000-0000-0000-0000-000000000001' , 'ParkingAreaOne' , CURRENT_TIMESTAMP 
    union all select '30000000-0000-0000-0000-000000000002' , 'ParkingAreaTwo' , CURRENT_TIMESTAMP 


INSERT INTO [dbo].[EmployeeParkingAreaLink] ( [TheEmployeeUUID] , [TheParkingAreaUUID]) 
     Select '20000000-0000-0000-0000-000000000001' , '30000000-0000-0000-0000-000000000001' 
union all  Select '20000000-0000-0000-0000-000000000002' , '30000000-0000-0000-0000-000000000002' 

凡約束 「CONST_UNIQUE_EmpUUID」 是你們之間的對話設置。

EmployeeEntity這樣的:

[Serializable] 
public partial class EmployeeEFEntity 
{ 

public EmployeeEFEntity() 
{ 
    CommonConstructor(); 
} 
private void CommonConstructor() 
{ 
    //this.MyParkingAreas = new List<ParkingAreaEFEntity>(); 
} 



//EF Tweaks 
public virtual Guid? ParentDepartmentUUID { get; set; } 

public virtual Guid? EmployeeUUID { get; set; } 

public virtual byte[] TheVersionProperty { get; set; } 

public virtual DepartmentEFEntity ParentDepartment { get; set; } 

public virtual string SSN { get; set; } 
public virtual string LastName { get; set; } 
public virtual string FirstName { get; set; } 
public virtual DateTime CreateDate { get; set; } 
public virtual DateTime HireDate { get; set; } 

public virtual ICollection<ParkingAreaEFEntity> MyParkingAreas { get; set; } 

public ParkingAreaEFEntity MyOneParkingAreaEFEntity { 

    get 
    { 
     return MyParkingAreas.FirstOrDefault(); 
    } 
    set 
    { 
     /* check for more than one here */ 
     this.AddParkingArea(pa); 
    } 
} 


public virtual void AddParkingArea(ParkingAreaEFEntity pa) 
{ 
    if (!pa.MyEmployees.Contains(this)) 
    { 
     pa.MyEmployees.Add(this); 
    } 
    if (!this.MyParkingAreas.Contains(pa)) 
    { 
     this.MyParkingAreas.Add(pa); 
    } 
} 


public virtual void RemoveParkingArea(ParkingAreaEFEntity pa) 
{ 
    if (pa.MyEmployees.Contains(this)) 
    { 
     pa.MyEmployees.Remove(this); 
    } 
    if (this.MyParkingAreas.Contains(pa)) 
    { 
     this.MyParkingAreas.Remove(pa); 
    } 
} 


public override string ToString() 
{ 
    return string.Format("{0}:{1},{2}", this.SSN, this.LastName, this.FirstName); 
} 

你會映射這樣的:

public class EmployeeMap : EntityTypeConfiguration<EmployeeEFEntity> 
{ 
    public EmployeeMap() 
    { 
     // Primary Key 
     this.HasKey(t => t.EmployeeUUID); 

     this.Property(t => t.EmployeeUUID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

     // Properties 
     this.Property(t => t.TheVersionProperty) 
      .IsRequired() 
      .IsFixedLength() 
      .HasMaxLength(8) 
      .IsRowVersion(); 

     this.Property(t => t.SSN) 
      .IsRequired() 
      .HasMaxLength(11); 

     this.Property(t => t.LastName) 
      .IsRequired() 
      .HasMaxLength(64); 

     this.Property(t => t.FirstName) 
      .IsRequired() 
      .HasMaxLength(64); 

     // Table & Column Mappings 
     this.ToTable("Employee"); 
     this.Property(t => t.EmployeeUUID).HasColumnName("EmployeeUUID"); 
     this.Property(t => t.ParentDepartmentUUID).HasColumnName("ParentDepartmentUUID"); 
     this.Property(t => t.TheVersionProperty).HasColumnName("TheVersionProperty"); 
     this.Property(t => t.SSN).HasColumnName("SSN"); 
     this.Property(t => t.LastName).HasColumnName("LastName"); 
     this.Property(t => t.FirstName).HasColumnName("FirstName"); 
     this.Property(t => t.CreateDate).HasColumnName("CreateDate"); 
     this.Property(t => t.HireDate).HasColumnName("HireDate"); 

     // Relationships 
     this.HasMany(t => t.MyParkingAreas) 
      .WithMany(t => t.MyEmployees) 
      .Map(m => 
      { 
       m.ToTable("EmployeeParkingAreaLink"); 
       m.MapLeftKey("TheEmployeeUUID"); 
       m.MapRightKey("TheParkingAreaUUID"); 
      }); 

     this.HasRequired(t => t.ParentDepartment) 
      .WithMany(t => t.Employees) 
      .HasForeignKey(d => d.ParentDepartmentUUID); 

    } 
} 

ParkingArea這樣的:

[Serializable] 
public partial class ParkingAreaEFEntity 
{ 

    public ParkingAreaEFEntity() 
    { 
     CommonConstructor(); 
    } 
    private void CommonConstructor() 
    { 
     //this.MyEmployees = new List<EmployeeEFEntity>(); 
    } 

    public virtual Guid ParkingAreaUUID { get; set; } 


    public virtual string ParkingAreaName { get; set; } 
    public virtual DateTime CreateDate { get; set; } 

    public virtual ICollection<EmployeeEFEntity> MyEmployees { get; set; } 

    public virtual void AddEmployee(EmployeeEFEntity emp) 
    { 
     if (!emp.MyParkingAreas.Contains(this)) 
     { 
      emp.MyParkingAreas.Add(this); 
     } 
     if (!this.MyEmployees.Contains(emp)) 
     { 
      this.MyEmployees.Add(emp); 
     } 
    } 

    public virtual void RemoveEmployee(EmployeeEFEntity emp) 
    { 
     if (emp.MyParkingAreas.Contains(this)) 
     { 
      emp.MyParkingAreas.Remove(this); 
     } 
     if (this.MyEmployees.Contains(emp)) 
     { 
      this.MyEmployees.Remove(emp); 
     } 
    } 
+0

雖然這個例子可能已經被大大簡化了,但我認爲它確實展示了你對EF的「說謊」的評論,並說聯合名字是爲了多對多的關係。我想強調其他讀者的重要部分......即在'EmployeeMap'類(這是'EmployeeEFEntity'的流暢EF配置),'HasMany。( - )。WithMany( - )。Map( - )'是我們對EF說謊並指定連接表的地方。 –

+0

另外,在我的設置中(雖然我將使用您的示例中的命名),但我需要添加配置Ignore(x => x.MyOneParkingAreaEFEntity),以便EF不會認爲數據庫中存在列名稱爲MyOneParkingAreaEFEntity_ID。 –

+0

這就是我爲POC而保留的例子。一旦你解決了1:N,一個M:N(關係中沒有屬性),一個M:N(關係上有屬性)和一個查找表.......大多數東西都可以從。 – granadaCoder

2

您要尋找的是可選的一對多關聯連接表。這背後的動機是我們總是試圖避免關係數據庫模式中的可空列。未知的信息會降低您存儲的數據的質量。因此,一個可選的實體關聯,無論是一對一還是一對多,最好用一個連接表在SQL數據庫中表示以避免可爲空的外鍵列。

就這麼說,EF不幸的是不是支持這種類型的映射。如果你真的想實現這一點,那麼你可能想看看其他ORM框架,支持一個連接表,如NHibernate一對多關聯。