2013-09-26 199 views
6

Sự cố này dễ dàng được giải quyết trong ORM như Entity Framework hoặc NHibernate, nhưng tôi không thấy bất kỳ giải pháp làm sẵn nào trong trình điều khiển C# cho MongoDb. Giả sử tôi có bộ sưu tập các đối tượng kiểu A tham chiếu các đối tượng loại B mà tôi cần lưu trữ trong bộ sưu tập riêng biệt, để một khi đối tượng B cụ thể bị thay đổi, tất cả tham chiếu A cần phải biết sự thay đổi. Nói cách khác, tôi cần quan hệ đối tượng này được chuẩn hóa. Trong cùng một thời điểm tôi cần B đang được tham chiếu bởi A bên trong lớp, không phải bởi Id, nhưng theo loại tài liệu tham khảo như hình dưới đây:Làm thế nào để đối phó với việc tham chiếu các đối tượng được lưu trữ riêng trong các DB tài liệu như Mongo?

public class A 
{ 
    public B RefB { get; set; } 
} 

Tôi có phải xử lý tất cả điều này nhất quán tham khảo trên của riêng tôi? Nếu vậy, cách tiếp cận nào là tốt nhất để sử dụng? Tôi có phải giữ cả Id B và tài liệu tham khảo B trong lớp và bằng cách nào đó chăm sóc đồng bộ giá trị của chúng như thế:

public class A 
{ 
    // Need to implement reference consistency as well 
    public int RefBId { get; set; } 

    private B _refB; 
    [BsonIgnore] 
    public B RefB 
    { 
     get { return _refB; } 
     set { _refB = value; RefBId = _refB.Id } 
    } 
} 

Tôi biết ai đó có thể nói cơ sở dữ liệu quan hệ gặp trường hợp này là tốt nhất, tôi biết, nhưng tôi thực sự phải sử dụng tài liệu Db như MongoDb, nó giải quyết được nhiều vấn đề, và trong hầu hết các trường hợp, tôi cần lưu trữ các đối tượng không chuẩn hóa cho dự án của mình, tuy nhiên đôi khi chúng ta có thể cần thiết kế hỗn hợp bên trong bộ nhớ đơn.

Trả lời

6

Đây chủ yếu là mối quan tâm về kiến ​​trúc và có thể phụ thuộc vào sở thích cá nhân một chút. Tôi sẽ cố gắng kiểm tra những ưu điểm và nhược điểm (thực sự chỉ có nhược điểm, điều này khá là có ý kiến):

Ở cấp độ cơ sở dữ liệu, MongoDB không cung cấp công cụ để thực thi tính toàn vẹn tham chiếu, vì vậy có, bạn phải tự làm điều này. Tôi khuyên bạn nên sử dụng các đối tượng cơ sở dữ liệu trông giống như sau:

public class DBObject 
{ 
    public ObjectId Id {get;set;} 
} 

public class Department : DBObject 
{ 
    // ... 
} 

public class EmployeeDB : DBObject 
{ 
    public ObjectId DepartmentId {get;set;} 
} 

Tôi khuyên bạn nên sử dụng DTO thuần túy như thế này ở cấp cơ sở dữ liệu. Nếu bạn muốn thêm đường, đặt nó trong một lớp riêng biệt ngay cả khi đó có nghĩa là một chút sao chép. Logic trong các đối tượng DB đòi hỏi một sự hiểu biết rất tốt về cách trình điều khiển hydrate đối tượng và có thể yêu cầu phải dựa vào chi tiết thực hiện.

Bây giờ, bạn có muốn làm việc với các đối tượng 'thông minh' hơn không. Thật vậy, nhiều người thích sử dụng các trình truy cập tự động kích hoạt mạnh mẽ, ví dụ:

public class Employee 
{ 
    public Department 
    { get { return /* the department object, magically, from the DB */ } } 
} 

mẫu này đi kèm với một số thách thức:

  • Nó đòi hỏi lớp Employee, một lớp mô hình, để có thể làm ẩm các đối tượng từ cơ sở dữ liệu. Đó là khó khăn, bởi vì nó cần phải có DB tiêm hoặc bạn cần một đối tượng tĩnh để truy cập cơ sở dữ liệu mà cũng có thể được khôn lanh.
  • Truy cập Department trông hoàn toàn rẻ, nhưng trên thực tế, nó kích hoạt hoạt động cơ sở dữ liệu, nó có thể chậm, có thể không thành công. Điều này hoàn toàn ẩn khỏi người gọi.
  • Trong mối quan hệ 1: n, mọi thứ trở nên phức tạp hơn rất nhiều. Ví dụ: Department cũng có hiển thị danh sách Employees không? Nếu có, danh sách đó có thực sự là danh sách (tức là khi bạn bắt đầu đọc lần đầu tiên, tất cả nhân viên phải được deserialized không?) Hoặc có phải là số MongoCursor lười biếng không?
  • Để làm cho vấn đề tồi tệ hơn, thường không rõ loại bộ nhớ đệm nào nên được sử dụng. Giả sử bạn nhận được myDepartment.Employee[0].Department.Name. Rõ ràng, mã này không phải là thông minh, nhưng hãy tưởng tượng có một ngăn xếp cuộc gọi với một vài phương pháp chuyên ngành. Họ có thể gọi mã giống như vậy, ngay cả khi nó ẩn hơn. Bây giờ một triển khai ngây thơ sẽ thực sự de-serialize ref'd Department một lần nữa. Đó là xấu xí. Mặt khác, bộ nhớ đệm tích cực là nguy hiểm bởi vì bạn thực sự có thể muốn để tìm nạp lại đối tượng.
  • Điều tồi tệ nhất: Cập nhật. Cho đến nay, những thách thức phần lớn là chỉ đọc. Bây giờ, giả sử tôi gọi employeeJohn.Department.Name = 'PixelPushers'employeeJohn.Save(). Điều đó có cập nhật Bộ hay không? Nếu có, các thay đổi đối với john được tuần tự hóa lần đầu tiên hay sau khi thay đổi đối tượng phụ thuộc? Điều gì về versioning và khóa?
  • Nhiều ngữ nghĩa khó thực hiện: employeJohn.Department.Employees.Clear() có thể phức tạp.

Nhiều ORM sử dụng một tập hợp các mẫu phức tạp để cho phép các hoạt động này, do đó, những vấn đề này không thể làm việc xung quanh. Nhưng ORM thường nằm trong khoảng 100k đến hơn 1M dòng mã (!), Và tôi nghi ngờ bạn có loại thời gian đó. Trong một RDBMS, cần phải kích hoạt các đối tượng liên quan và sử dụng sth. như ORM nghiêm trọng hơn nhiều, bởi vì bạn không thể nhúng ví dụ danh sách các chi tiết đơn hàng trong hóa đơn, vì vậy, mỗi mối quan hệ 1: n hoặc m: n phải được thể hiện bằng cách sử dụng một kết nối. Đó được gọi là sự không phù hợp với đối tượng.

Ý tưởng về cơ sở dữ liệu tài liệu, như tôi đã hiểu, là bạn không cần phải phá vỡ mô hình của mình một cách không tự nhiên như bạn có trong RDBMS. Tuy nhiên, có 'biên giới đối tượng'. Nếu bạn nghĩ về mô hình dữ liệu của mình như một mạng lưới các nút được kết nối, thì thách thức là phải biết phần nào của dữ liệu bạn hiện đang làm việc.

Cá nhân, tôi không muốn đặt một lớp trừu tượng lên trên, vì trừu tượng đó bị rò rỉ, nó giấu những gì thực sự xảy ra với người gọi và cố gắng giải quyết mọi vấn đề với cùng một cái búa.

Một phần của ý tưởng NoSQL là các mẫu truy vấn của bạn phải được kết hợp cẩn thận với mô hình dữ liệu, vì bạn không thể áp dụng búa JOIN cho bất kỳ bảng nào trong tầm nhìn.

Vì vậy, ý kiến ​​của tôi là: dính vào một lớp mỏng và thực hiện hầu hết hoạt động cơ sở dữ liệu trong một lớp dịch vụ. Di chuyển DTO xung quanh thay vì thiết kế một mô hình miền phức tạp tách rời ngay khi bạn cần thêm khóa, mvcc, cập nhật xếp chồng, v.v.

3

Trong một cơ sở dữ liệu tài liệu, khi bạn làm điều gì đó giống như ví dụ đầu tiên của bạn:

public class A 
{ 
    public B RefB { get; set; } 
} 

Bạn đang nhúng hoàn toàn giá trị của B vào tài sản RefB. Nói cách khác, tài liệu của bạn trông giống như sau:

[a/1] 
{ 
    AProp: "foo", 
    RefB: { 
     BProp: "bar" 
    } 
} 

Giúp xem xét mọi thứ từ quan điểm Thiết kế dựa trên miền (DDD). Mẫu nhúng này thường xảy ra khi B là một "đối tượng giá trị" hoặc "thực thể không tổng hợp" (sử dụng thuật ngữ DDD).

Điều này cũng có thể xảy ra nếu bạn lưu trữ ảnh chụp nhanh theo thời gian của một số thực thể tổng hợp khác. Trong trường hợp đó, bạn không muốn cập nhật các giá trị của B nếu chúng thay đổi hoặc không còn đại diện cho thời điểm đó nữa.

Mẫu khác sẽ là xử lý AB làm các tập hợp riêng biệt. Nếu cần phải tham chiếu đến tài khoản còn lại, bạn chỉ định rằng chỉ có tham chiếu đến ID của nó.

public class A 
{ 
    public string BId { get; set; } 
} 

Tài liệu của bạn sau đó sẽ được lưu trữ như:

[a/1] 
{ 
    AProp: "foo", 
    BId: "b/2" 
} 

[b/2] 
{ 
    BProp: "bar", 
} 

Lưu ý: Tôi tin vào MongoDB, bạn sẽ sử dụng một loại ObjectId. Trong RavenDB, bạn thường sẽ sử dụng một số string, nhưng một số int là có thể với một chút điều chỉnh nhỏ. Cơ sở dữ liệu tài liệu khác có thể cho phép các loại khác.

Phần điều đó không làm việc tốt trong cơ sở dữ liệu tài liệu là cách bạn thể hiện trong ví dụ thứ hai của bạn A giữ một tài liệu tham khảo -B mà không giữ nó như là một phần của tài liệu. Mẫu này có thể hoạt động trong các ORM như Entity Framework hoặc NHibernate, nhưng nó có xu hướng được triển khai thông qua các thuộc tính ảo và virtual và các lớp proxy. Những người không nắm giữ tốt trong một môi trường cơ sở dữ liệu tài liệu.

Vì vậy, nếu họ là các tài liệu riêng biệt, thay vì tải A và sử dụng a.RefB để có được B, bạn sẽ chỉ tải AB riêng. Ví dụ: bạn có thể tải A và sử dụng BId để xác định cách tải B.

Tất nhiên, câu hỏi vẫn tiếp tục là có nhúng hay liên kết hay không. Đó là một cái gì đó bạn sẽ phải tìm ra, vì nó thường có thể được thực hiện một trong hai cách. Thông thường một cách hoạt động tốt hơn cách khác đối với một mối quan tâm miền cụ thể. Nhưng bạn thường không làm cả hai.

1

Cơ sở dữ liệu tài liệu dựa trên các khái niệm kiến ​​trúc hoàn toàn khác với cơ sở dữ liệu quan hệ. Nguyên tắc chính của cơ sở dữ liệu NoSQL là tổng hợp và không liên quan. Vì vậy, bạn không nên mong đợi bình thường hóa trong db như bạn mô tả.

Sự cố của bạn chỉ nên được theo dõi bằng tay. Không có điều như vậy trong NoSQL như tính toàn vẹn tham chiếu.