2016-06-13 17 views
1

Im Datenbankentwurf ist es üblich, eine Eins-zu-viele-Beziehung mit einem Fremdschlüssel auf der "Ein" -Seite (oder Kindseite) auszudrücken. Entity Framework behandelt diese Situation gut.Express-Eins-zu-Viele-Beziehung in Entity Framework über Join-Tabelle

Ich habe jedoch die Situation, in der eine Eins-zu-viele-Beziehung über eine Join-Tabelle ausgedrückt wird, in der einer der beiden Fremdschlüssel in der Tabelle eine eindeutige Einschränkung hat.

Wie kann Entity Framework für die Verwendung dieser Join-Tabelle konfiguriert werden?

In meinem aktuellen Zustand, wenn eine einfache Leseabfrage für die one/child Entität, Entity Frameworks wirft eine Ausnahme --- wie erwartet ---, dass die one/child Tabelle eine Spalte durch den konventionellen Namen fehlt basierend auf der Navigationseigenschaft.

+0

Wenn Sie einen Tisch beitreten ..... dann (Auch wenn dort db-Einschränkungen vorhanden sind, um ein 1: N zu garantieren), müssen Sie den EF-Teil als M: N behandeln. Sie können eine (get only) Eigenschaft auf der EF-Entität schreiben, die den FirstOrDefault() zurückbringt, der Ihre "Eins" im 1: N zurückbringt. – granadaCoder

+0

@granadaCoder Oo, das mag ich. Ich werde es versuchen und melden. –

+0

@granadaCoder Aber es sollte eine Get-only-Eigenschaft sein. Es sollte einen Setter haben, der es der Sammlung hinzufügt. –

Antwort

1

Wenn ich die unten DDL habe.

Mitarbeiter (M: N) zu ParkingArea. Eine Einschränkung hält jedoch nur einen Mitarbeiter in der Verknüpfungstabelle, also eine 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' 

Wo die Einschränkung "CONST_UNIQUE_EmpUUID" ist die Einrichtung, von denen Sie sprechen.

EmployeeEntity wie folgt aus:

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

Sie mögen diese Karte würde:

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 wie folgt aus:

[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

Obwohl dieses Beispiel stark vereinfacht worden sein könnte, denke ich, dass es Ihren Kommentar zum "Lügen" bei EF zeigt und sagen, dass der Join-Name für eine Viele-zu-Viele-Beziehung ist. Ich möchte den wichtigen Teil für andere Leser hervorheben ... nämlich in der Klasse 'EmployeeMap' (was die fließende EF-Konfiguration für' EmployeeEFEntity' ist), die 'HasMany. (-). WithMany (-). Map (-) 'ist, wo wir zu EF" liegen "und die Join-Tabelle spezifizieren. –

+0

Auch in meinem Setup (obwohl ich die Benennung in Ihrem Beispiel verwenden werde), musste ich die Konfiguration 'Ignore (x => x.MyOneParkingAreaEFEntity)' hinzufügen, so dass EF nicht denkt, dass es eine Spalte in der Datenbank gibt der Name 'MyOneParkingAreaEFEntity_ID'. –

+0

Das ist das Beispiel, das ich für POC-Sachen behalte. Sobald Sie ein 1: N, ein M: N (ohne Attribute für die Beziehung), ein M: N (mit Attributen für die Beziehung) und eine Nachschlagetabelle lösen ....... können die meisten Dinge daraus abgeleitet werden . – granadaCoder

2

Was Sie suchen, ist optionale Eins-zu-viele-Verbindung mit einer Join-Tabelle. Die Motivation dahinter ist, dass wir immer versuchen, nullwertfähige Spalten in einem relationalen Datenbankschema zu vermeiden. Informationen, die unbekannt sind, verschlechtern die Qualität der Daten, die Sie speichern. Daher ist eine optionale Entitätsassoziation, ob Eins-zu-Eins oder Eins-zu-Viele, am besten in einer SQL-Datenbank mit einer Join-Tabelle dargestellt, um Nullable-Fremdschlüsselspalten zu vermeiden.

All das gesagt, EF leider unterstützt nicht diese Art der Zuordnung. Wenn Sie dies wirklich implementieren möchten, sollten Sie sich vielleicht andere ORM-Frameworks ansehen, die eine Eins-zu-Viele-Verknüpfung mit einer Join-Tabelle wie NHibernate unterstützen.