2015-09-15 16 views
8

Để tránh sử dụng Bảng phân cấp (TPH), tôi đã xem xét các ví dụ về cách thực hiện tốt nhất Lớp mỗi lớp bê tông (TPC) thừa kế trong mô hình cơ sở dữ liệu của tôi. Tôi đã xem qua số official documentationthis article.Thừa kế bảng trên mỗi loại bê tông (TPC) trong khuôn khổ thực thể 6 (EF6)

Dưới đây là một số lớp mô hình với một số thừa kế đơn giản.

public class BaseEntity 
{ 
    public BaseEntity() 
    { 
     ModifiedDateTime = DateTime.Now; 
    } 

    [Key] 
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] 
    public int Id { get; set; } 

    public DateTime ModifiedDateTime { get; set; } 
} 

public class Person : BaseEntity 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
} 

public class Business : BaseEntity 
{ 
    public string Name { get; set; } 
    public string Location { get; set; } 
} 

Cấu hình DbModelBuilder được sử dụng cho mỗi ví dụ trong cả hai bài viết.

modelBuilder.Entity<BaseEntity>() 
    .Property(c => c.Id) 
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 

modelBuilder.Entity<Person>().Map(m => 
{ 
    m.MapInheritedProperties(); 
    m.ToTable("Person"); 
}); 

modelBuilder.Entity<Business>().Map(m => 
{ 
    m.MapInheritedProperties(); 
    m.ToTable("Business"); 
}); 

Ứng dụng chạy thành công nhưng khi tôi quay trở lại cơ sở dữ liệu, tôi tìm thấy ba (3) bảng thay vì hai (2) tôi dự kiến ​​sẽ tìm thấy. Sau một chút thử nghiệm nó sẽ xuất hiện bảng "BaseEntity" được tạo ra nhưng không bao giờ được sử dụng. Mọi thứ dường như hoạt động tốt với ngoại lệ của cái bàn trống mồ côi này.

Tôi gây rối với cấu hình DbModelBuilder, cuối cùng xóa cấu hình "BaseEntity" cung cấp kết quả mong đợi; Hai (2) bảng, mỗi bảng có các thuộc tính chính xác và hoạt động chính xác.

Tôi làm một thử nghiệm cuối cùng, trích xuất tất cả cấu hình DbModelBuilder, chỉ bao gồm hai (2) thuộc tính DbSet cho "Người" và "Doanh nghiệp" và kiểm tra lại.

public DbSet<Person> People { get; set; } 
public DbSet<Business> Businesses { get; set; } 

Tôi ngạc nhiên khi dự án xây dựng, đi đến cơ sở dữ liệu, chỉ tạo hai bảng với tất cả các thuộc tính lớp bao gồm các bảng được thừa kế từ lớp "BaseEntity". Tôi có thể thực hiện các hoạt động CRUD mà không gặp vấn đề gì.

Sau khi chạy nhiều kiểm tra, tôi không thể tìm thấy bất kỳ vấn đề nào với thử nghiệm cuối cùng và tôi đã không thể tạo lại lỗi khóa trùng lặp cả hai bài báo được cảnh báo.

Các thay đổi đối với cơ sở dữ liệu đã được cam kết thành công, nhưng lỗi xảy ra khi cập nhật ngữ cảnh đối tượng. ObjectContext có thể là ở trạng thái không nhất quán. Thông báo ngoại lệ bên trong: AcceptChanges không thể tiếp tục vì các giá trị khóa của đối tượng xung đột với đối tượng khác trong ObjectStateManager. Đảm bảo rằng các giá trị khóa là duy nhất trước khi gọi AcceptChanges.

  1. Tôi tò mò tại sao các ví dụ sử dụng thuộc tính MapInheritedProperties; đây có phải là phương pháp lỗi thời không?
  2. Tại sao cả hai ví dụ nói để bao gồm các thuộc tính cấu hình cho "BaseEntity" nhưng bao gồm thuộc tính DbSet hoặc bất kỳ cấu hình DbModelBuilder nào cho lớp "BaseEntity" làm cho bảng không được sử dụng được tạo ra.
  3. Trong tham chiếu đến lỗi khóa duy nhất các bài báo được cảnh báo; Tôi không thể tái tạo lỗi và tôi đã thử nghiệm nhiều lần với khóa chính như là một int được tạo ra bởi cơ sở dữ liệu và một guid được tạo ra bởi cơ sở dữ liệu. Là thông tin về lỗi này cũng đã lỗi thời hoặc có một thử nghiệm tôi có thể chạy để tạo ra lỗi đã nói không?
+0

Tôi nghĩ rằng nên có 3 bảng được tạo, mặc dù cơ sở có vẻ không sử dụng được. đó là một số loại khuyết điểm của TPC, bạn có thể mất một số lợi ích trong mã máy khách nhưng phải chịu một số dữ liệu thừa (không nên được tạo ngay cả trong khái niệm thiết kế cơ sở dữ liệu truyền thống). Tôi nghĩ rằng bạn có thể thử TPT thay vì trong khi bảng cơ sở chứa tất cả các cột phổ biến và nó sẽ không được coi là dư thừa nữa. – Hopeless

+1

@Hopeless Lý do tôi không xem xét TPT, nhiều như tôi yêu khía cạnh thiết kế là OCD như tôi, là hiệu suất hit bạn lấy từ tất cả các tuyên bố tham gia cần thiết để kéo trở lại ngay cả dữ liệu hơi phức tạp. Đối với tuyên bố về 3 bảng, tôi đã nhìn thấy rất nhiều bài viết ngược lại nhưng một lần nữa không có gì tôi có thể làm việc một cách chính xác. Tôi tự hỏi nếu có một số bất lợi cho cách cuối cùng tôi thiết lập các mô hình; Tôi đã kết thúc với hai bảng, không có bảng rác, không có cột rác, không có vấn đề chính chính, nhưng tôi không thể tìm thấy ví dụ nào làm những gì tôi đã làm. – Nicholas

+0

Đối với TPH, lớp BaseEntity phải trừu tượng. Tôi nghĩ rằng một mình chỉ nên tạo ra hai bảng DB cho các loại cụ thể. Tôi thực sự đã chạy vào ObjectContext này đang ở trong một lỗi nhà nước không phù hợp, và cách duy nhất tôi đã có thể sửa chữa nó là để làm cho trường Id trong BaseEntity loại Guid thay vì Int (mà có vẻ khá lãng phí). – blgrnboy

Trả lời

2

Để làm điều này đơn giản hơn, tôi đã di chuyển mã cần thiết để buộc TablePerConcrete thành nguồn mở.Mục đích của nó là cho phép các tính năng thường chỉ có sẵn trong giao diện thông thạo (nơi bạn phải phân tán rất nhiều mã vào phương thức OnModelCreating của lớp Db) để di chuyển sang các tính năng dựa trên thuộc tính.

Nó cho phép bạn làm những việc như thế này:

[TablePerConcrete] 
public class MySubclassTable : MyParentClassEntity 

Buộc TPC bất kể những gì EF có thể quyết định để suy ra từ/mối quan hệ lớp con lớp cha mẹ của bạn.

Một thách thức thú vị ở đây là đôi khi EF sẽ vít lên một thuộc tính Id được kế thừa, đặt nó thành một giá trị rõ ràng hơn là tạo cơ sở dữ liệu. Bạn có thể đảm bảo rằng nó không làm điều đó bằng cách có lớp cha thực hiện giao diện IId (mà chỉ nói: Điều này có một thuộc tính Id), sau đó đánh dấu các lớp con với [ForcePKId].

public class MyParentClassEntity : IId 
{ 
    public int Id { get; set; } 
    . . . 

[TablePerConcrete] 
[ForcePKId] 
public class MySubclassTable : MyParentClassEntity 
{ 
    // No need for PK/Id property here, it was inherited and will work as 
    // you intended. 

Kicking tắt mã để xử lý tất cả điều này cho bạn là khá đơn giản - chỉ cần thêm một vài dòng đến lớp Db của bạn:

public class Db : DbContext 
{ 
    . . . 
    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     var modelsProject = Assembly.GetExecutingAssembly(); 
     B9DbExtender.New().Extend(modelBuilder, modelsProject); 

Bạn có thể truy cập vào nó một trong 2 cách sau:

  1. Thông qua một ý chính duy nhất với tất cả các lớp liên quan được sao chép dán vào một tệp, tại đây: https://gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9

  2. Qua thư viện, Brass9.Data của chúng tôi, chúng tôi sẽ phát hành mã nguồn mở. Nó có rất nhiều công cụ EF6 khác trong đó, như Di chuyển dữ liệu. Nó cũng được tổ chức hơn, với các lớp học được chia nhỏ thành các tệp riêng biệt như bạn thường mong đợi: https://github.com/b9chris/Brass9.Data

+1

Mã được liên kết bị thiếu "ReflectionHelper" –

+0

@AdamGreen Đã sửa lỗi!Xin lỗi vì điều đó. –

1

Tôi sử dụng các lớp ánh xạ, nhưng đừng bận tâm. Tôi giải quyết nó như sau:

public class PersonMap : EntityTypeConfiguration<Person> 
{ 
    public PersonMap() 
    { 
     Map(m => { m.ToTable("Person"); m.MapInheritedProperties(); }); 

     HasKey(p => p.Id); 
     Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 
    } 

} 

Hãy nhớ rằng - lớp cơ sở phải trừu tượng.

Các vấn đề liên quan