Mối quan hệ 1-1 tùy chọn sử dụng Entity Framework Fluent API


79

Chúng tôi muốn sử dụng mối quan hệ một đối một tùy chọn bằng cách sử dụng Mã khung thực thể trước. Chúng tôi có hai thực thể.

public class PIIUser
{
    public int Id { get; set; }

    public int? LoyaltyUserDetailId { get; set; }
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    public int Id { get; set; }
    public double? AvailablePoints { get; set; }

    public int PIIUserId { get; set; }
    public PIIUser PIIUser { get; set; }
}

PIIUsercó thể có a LoyaltyUserDetailnhưng LoyaltyUserDetailphải có a PIIUser. Chúng tôi đã thử các kỹ thuật tiếp cận thông thạo này.

modelBuilder.Entity<PIIUser>()
            .HasOptional(t => t.LoyaltyUserDetail)
            .WithOptionalPrincipal(t => t.PIIUser)
            .WillCascadeOnDelete(true);

Cách tiếp cận này không tạo LoyaltyUserDetailIdkhóa ngoại trong PIIUsersbảng.

Sau đó, chúng tôi đã thử đoạn mã sau.

modelBuilder.Entity<LoyaltyUserDetail>()
            .HasRequired(t => t.PIIUser)
            .WithRequiredDependent(t => t.LoyaltyUserDetail);

Nhưng lần này EF không tạo khóa ngoại nào trong 2 bảng này.

Bạn có bất kỳ ý tưởng cho vấn đề này? Làm cách nào chúng ta có thể tạo mối quan hệ 1-1 bằng cách sử dụng api thông thạo khung thực thể?

Câu trả lời:


101

EF Code First hỗ trợ 1:1và các 1:0..1mối quan hệ. Cái sau là những gì bạn đang tìm kiếm ("một đến không-hoặc-một").

Nỗ lực của bạn để thông thạo nói được yêu cầu ở cả hai đầu trong một trường hợp và tùy chọn ở cả hai đầu trong trường hợp kia.

Những gì bạn cần là tùy chọn ở một đầu và bắt buộc ở đầu kia.

Đây là một ví dụ từ cuốn sách Lập trình EF Code First

modelBuilder.Entity<PersonPhoto>()
.HasRequired(p => p.PhotoOf)
.WithOptional(p => p.Photo);

Thực PersonPhotothể có thuộc tính điều hướng được gọi là PhotoOftrỏ đến một Personkiểu. Các Personloại có một tài sản chuyển hướng gọi Phototrỏ đến các PersonPhotoloại.

Trong hai lớp liên quan, bạn sử dụng khóa chính của mỗi loại , không sử dụng khóa ngoại . tức là, bạn sẽ không sử dụng các thuộc tính LoyaltyUserDetailIdhoặc PIIUserId. Thay vào đó, mối quan hệ phụ thuộc vào các Idtrường của cả hai loại.

Nếu bạn đang sử dụng API thông thạo như trên, bạn không cần chỉ định LoyaltyUser.Idlàm khóa ngoại, EF sẽ tìm ra.

Vì vậy, nếu không có mã của bạn để tự kiểm tra (tôi ghét làm điều này từ đầu) ... Tôi sẽ dịch điều này thành mã của bạn dưới dạng

public class PIIUser
{
    public int Id { get; set; }    
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    public int Id { get; set; }
    public double? AvailablePoints { get; set; }    
    public PIIUser PIIUser { get; set; }
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<LoyaltyUserDetail>()
  .HasRequired(lu => lu.PIIUser )
  .WithOptional(pi => pi.LoyaltyUserDetail );
}

Điều đó nói rằng thuộc PIIUsertính LoyaltyUserDetails là bắt buộc và thuộc tính của PIIUser LoyaltyUserDetaillà tùy chọn.

Bạn có thể bắt đầu từ đầu bên kia:

modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);

hiện cho biết LoyaltyUserDetailtài sản của PIIUser là tùy chọn và PIIUsertài sản của LoyaltyUser là bắt buộc.

Bạn luôn phải sử dụng mẫu HAS / WITH.

Mối quan hệ HTH và FWIW, một đối một (hoặc một đến không / một) là một trong những mối quan hệ khó hiểu nhất để cấu hình trong mã đầu tiên, vì vậy bạn không đơn độc! :)


1
Điều đó không giới hạn người dùng chọn trường FK nào? (Tôi nghĩ anh ấy muốn điều đó, nhưng anh ấy chưa báo cáo lại nên tôi không biết), vì có vẻ như anh ấy muốn có lĩnh vực FK trong lớp.
Frans Bouma

1
Đã đồng ý. Đó là một hạn chế về cách thức hoạt động của EF. 1: 1 và 1: 0..1 phụ thuộc vào khóa chính. Nếu không, tôi nghĩ bạn đang lạc vào "FK duy nhất" vẫn không được hỗ trợ trong EF. :( (Bạn là một chuyên gia db hơn ... có chính xác không ... đây thực sự là về một FK duy nhất?) Và sẽ không có mặt trong Ef6 sắp tới theo: entityframework.codeplex.com/workitem/299
Julie Lerman,

1
Có @FransBouma, chúng tôi muốn sử dụng các trường PIIUserId và LoyaltUserId làm khóa ngoại. Nhưng EF giới hạn chúng tôi trong tình huống này như bạn và Julie đã đề cập. Cảm ơn vì những câu trả lời.
İlkay İlknur

@JulieLerman tại sao bạn sử dụng WithOptional? Khác biệt với WithRequiredDependentvà là WithRequiredOptionalgì?
Willy

1
WithOptional trỏ đến một mối quan hệ là tùy chọn, ví dụ: 0..1 (không hoặc một) vì OP cho biết "PIIUser có thể có LoyaltyUserDetail". Và sự nhầm lẫn của bạn dẫn đến câu hỏi thứ 2 là vì bạn đã sai một trong các thuật ngữ. ;) Đó là WithRequiredDependent và WithRequiredPrincipal. Điều đó có làm cho nó ý nghĩa hơn không? Trỏ đến một kết thúc bắt buộc là người phụ thuộc (còn gọi là "con") hoặc chính hay còn gọi là "cha mẹ". Ngay cả trong mối quan hệ 1-1 mà cả hai đều bình đẳng, EF cần coi một bên là chính & một bên là phụ thuộc. HTH!
Julie Lerman,

27

Cũng giống như nếu bạn có mối quan hệ một-nhiều LoyaltyUserDetailPIIUservì vậy bạn phải lập bản đồ

modelBuilder.Entity<LoyaltyUserDetail>()
       .HasRequired(m => m.PIIUser )
       .WithMany()
       .HasForeignKey(c => c.LoyaltyUserDetailId);

EF nên tạo tất cả khóa ngoại bạn cần và chỉ cần không quan tâm đến WithMany !


3
Câu trả lời của Julie Lerman đã được chấp nhận (và nên tiếp tục được chấp nhận, IMHO) vì nó trả lời câu hỏi và đi vào chi tiết về lý do tại sao đó là cách chính xác với chi tiết và thảo luận ủng hộ. Các câu trả lời sao chép và dán chắc chắn có sẵn trên khắp SO và bản thân tôi cũng đã sử dụng một vài câu, nhưng là một nhà phát triển chuyên nghiệp, bạn nên quan tâm hơn đến việc trở thành một lập trình viên giỏi hơn, không chỉ lấy mã để xây dựng. Điều đó nói rằng, jr đã đúng, mặc dù một năm sau đó.
Mike Devenney

1
@ClickOk Tôi biết đây là một câu hỏi và nhận xét cũ, nhưng đây không phải là câu trả lời chính xác vì nó sử dụng một đến nhiều như một cách giải quyết, điều này không tạo nên mối quan hệ 1-1 hoặc 1-1 với 0
Mahmoud Darwish

3

Có một số điều sai với mã của bạn.

Một 1: 1 mối quan hệ là một trong hai: PK <-PK , nơi một bên PK cũng là một FK, hoặc PK <-FK + UC , nơi phía FK là một tổ chức phi PK và có một UC. Mã của bạn cho thấy bạn có FK <-FK , vì bạn xác định cả hai bên để có FK nhưng điều đó sai. Tôi nhận ra PIIUserlà phe PK và LoyaltyUserDetaillà phe FK. Điều này có nghĩa là PIIUserkhông có trường FK, nhưng LoyaltyUserDetailcó.

Nếu mối quan hệ 1: 1 là tùy chọn, thì phía FK phải có ít nhất 1 trường có giá trị rỗng.

pswg ở trên đã trả lời câu hỏi của bạn nhưng đã mắc lỗi rằng anh ấy / anh ấy cũng đã xác định FK trong PIIUser, tất nhiên là sai như tôi đã mô tả ở trên. Vì vậy, hãy xác định trường FK có thể nullable trong LoyaltyUserDetail, xác định thuộc tính trong LoyaltyUserDetailđể đánh dấu nó là trường FK, nhưng không chỉ định trường FK trong PIIUser.

Bạn nhận được ngoại lệ mà bạn mô tả ở trên bên dưới bài đăng của pswg, bởi vì không có bên nào là bên PK (kết thúc nguyên tắc).

EF không tốt lắm với tỷ lệ 1: 1 vì nó không thể xử lý các ràng buộc duy nhất. Trước tiên, tôi không phải là chuyên gia về Code, vì vậy tôi không biết liệu nó có thể tạo UC hay không.

(sửa) btw: A 1: 1 B (FK) có nghĩa là chỉ có 1 ràng buộc FK được tạo, trên mục tiêu của B chỉ đến PK của A, không phải 2.


3
public class User
{
    public int Id { get; set; }
    public int? LoyaltyUserId { get; set; }
    public virtual LoyaltyUser LoyaltyUser { get; set; }
}

public class LoyaltyUser
{
    public int Id { get; set; }
    public virtual User MainUser { get; set; }
}

        modelBuilder.Entity<User>()
            .HasOptional(x => x.LoyaltyUser)
            .WithOptionalDependent(c => c.MainUser)
            .WillCascadeOnDelete(false);

điều này sẽ giải quyết vấn đề về THAM KHẢOTỪ KHÓA NGOẠI LỆ

khi CẬP NHẬT hoặc XÓA bản ghi


Điều này sẽ không hoạt động như mong đợi. Điều này dẫn đến mã di chuyển không sử dụng LoyaltyUserId làm khóa ngoại, vì vậy User.Id và LoyaltyUser.Id sẽ có cùng một giá trị và LoyaltyUserId sẽ bị để trống.
Aaron Queenan

2

Hãy thử thêm ForeignKeythuộc tính vào thuộc tính LoyaltyUserDetail:

public class PIIUser
{
    ...
    public int? LoyaltyUserDetailId { get; set; }
    [ForeignKey("LoyaltyUserDetailId")]
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
    ...
}

PIIUsertài sản:

public class LoyaltyUserDetail
{
    ...
    public int PIIUserId { get; set; }
    [ForeignKey("PIIUserId")]
    public PIIUser PIIUser { get; set; }
    ...
}

1
Chúng tôi đã thử chú thích dữ liệu trước tất cả các cách tiếp cận API thông thạo này. Nhưng chú thích dữ liệu không hoạt động. nếu bạn thêm chú thích dữ liệu mà bạn đã đề cập ở trên, EF sẽ ném ngoại lệ này => Không thể xác định kết thúc chính của một liên kết giữa các loại 'LoyaltyUserDetail' và 'PIIUser'. Phần cuối chính của liên kết này phải được định cấu hình rõ ràng bằng cách sử dụng API thông thạo mối quan hệ hoặc chú thích dữ liệu.
İlkay İlknur

@ İlkayİlknur Điều gì sẽ xảy ra nếu bạn chỉ thêm ForeignKeythuộc tính vào một đầu của mối quan hệ? tức là chỉ trên PIIUser hoặc LoyaltyUserDetail .
pswg

Ef ném cùng một ngoại lệ.
İlkay İlknur

1
@pswg Tại sao FK tại PIIUser? LoyaltyUserDetail có FK, không phải PIIUser. Vì vậy, nó phải là [Key, ForeignKey ("PIIUser")] tại thuộc tính PIIUserId của LoyaltyUserDetail. Hãy thử cái này
user808128

@ user808128 Bạn có thể đặt chú thích trên thuộc tính điều hướng hoặc ID, không có sự khác biệt.
Thomas Boby

1

Một điều gây nhầm lẫn với các giải pháp trên là Khóa chính được định nghĩa là " Id" trong cả hai bảng và nếu bạn có khóa chính dựa trên tên bảng thì nó sẽ không hoạt động, tôi đã sửa đổi các lớp để minh họa giống nhau, tức là bảng tùy chọn không nên xác định khóa chính của riêng nó thay vào đó nên sử dụng cùng một tên khóa từ bảng chính.

public class PIIUser
{
    // For illustration purpose I have named the PK as PIIUserId instead of Id
    // public int Id { get; set; }
    public int PIIUserId { get; set; }

    public int? LoyaltyUserDetailId { get; set; }
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    // Note: You cannot define a new Primary key separately as it would create one to many relationship
    // public int LoyaltyUserDetailId { get; set; }

    // Instead you would reuse the PIIUserId from the primary table, and you can mark this as Primary Key as well as foreign key to PIIUser table
    public int PIIUserId { get; set; } 
    public double? AvailablePoints { get; set; }

    public int PIIUserId { get; set; }
    public PIIUser PIIUser { get; set; }
}

Và sau đó là

modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);

Sẽ thực hiện thủ thuật, giải pháp được chấp nhận không thể giải thích rõ ràng điều này và nó khiến tôi mất vài giờ để tìm ra nguyên nhân


1

Điều này không có ích gì đối với áp phích gốc, nhưng đối với bất kỳ ai vẫn đang sử dụng EF6 cần khóa ngoại khác với khóa chính, đây là cách thực hiện:

public class PIIUser
{
    public int Id { get; set; }

    //public int? LoyaltyUserDetailId { get; set; }
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    public int Id { get; set; }
    public double? AvailablePoints { get; set; }

    public int PIIUserId { get; set; }
    public PIIUser PIIUser { get; set; }
}

modelBuilder.Entity<PIIUser>()
    .HasRequired(t => t.LoyaltyUserDetail)
    .WithOptional(t => t.PIIUser)
    .Map(m => m.MapKey("LoyaltyUserDetailId"));

Lưu ý rằng bạn không thể sử dụng LoyaltyUserDetailIdtrường vì theo như tôi có thể nói, nó chỉ có thể được chỉ định bằng cách sử dụng API thông thạo. (Tôi đã thử ba cách để làm điều đó bằng cách sử dụng ForeignKeythuộc tính và không có cách nào trong số chúng hoạt động).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.