Các lựa chọn thay thế cho mẫu kho lưu trữ để đóng gói logic ORM?


24

Tôi vừa phải tắt ORM và đó là một nhiệm vụ tương đối khó khăn, bởi vì logic truy vấn bị rò rỉ ở khắp mọi nơi. Nếu tôi đã từng phải phát triển một ứng dụng mới, sở thích cá nhân của tôi sẽ là gói gọn tất cả logic truy vấn (sử dụng ORM) để tương lai hóa nó để thay đổi. Mẫu lưu trữ khá rắc rối để mã hóa và duy trì vì vậy tôi đã tự hỏi nếu có bất kỳ mẫu nào khác để giải quyết vấn đề?

Tôi có thể thấy trước các bài đăng về việc không thêm độ phức tạp trước khi thực sự cần thiết, nhanh nhẹn, v.v. nhưng tôi chỉ quan tâm đến các mẫu hiện có giải quyết vấn đề tương tự theo cách đơn giản hơn.

Suy nghĩ đầu tiên của tôi là có một kho lưu trữ kiểu chung, trong đó tôi thêm các phương thức khi cần thiết cho các lớp kho lưu trữ kiểu cụ thể thông qua các phương thức mở rộng, nhưng các phương thức tĩnh kiểm thử đơn vị là vô cùng đau đớn. I E:

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}

5
Chính xác thì nó là gì về mẫu Kho lưu trữ mà bạn thấy rắc rối khi viết mã và duy trì?
Matthew Flynn

1
Có những lựa chọn thay thế ngoài kia. Một trong số đó là sử dụng mẫu Đối tượng truy vấn mà mặc dù tôi chưa sử dụng có vẻ như là một cách tiếp cận tốt. Kho lưu trữ IMHO là một mẫu tốt nếu được sử dụng đúng nhưng không phải là tất cả và kết thúc tất cả
dreza

Câu trả lời:


14

Trước hết: Một kho lưu trữ chung nên được coi là một lớp cơ sở và không hoàn thành việc triển khai. Nó sẽ giúp bạn có được các phương pháp CRUD phổ biến tại chỗ. Nhưng bạn vẫn phải thực hiện các phương thức truy vấn:

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

Thứ hai:

Đừng quay lại IQueryable. Đó là một sự trừu tượng bị rò rỉ. Cố gắng tự thực hiện giao diện đó hoặc sử dụng nó mà không có kiến ​​thức về nhà cung cấp db cơ bản. Chẳng hạn, mọi nhà cung cấp db đều có API riêng cho các thực thể tải háo hức. Đó là lý do tại sao nó là một sự trừu tượng bị rò rỉ.

Thay thế

Để thay thế, bạn có thể sử dụng các truy vấn (ví dụ như được xác định bởi mẫu phân tách Lệnh / Truy vấn). CQS được mô tả trong wikipedia.

Về cơ bản bạn tạo các lớp truy vấn mà bạn gọi:

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

điều tuyệt vời với các truy vấn là bạn có thể sử dụng các phương thức truy cập dữ liệu khác nhau cho các truy vấn khác nhau mà không làm lộn xộn mã. ví dụ, bạn có thể sử dụng một dịch vụ web trong một, nhibernate trong một dịch vụ khác và ADO.NET trong một phần ba.

Nếu bạn quan tâm đến việc triển khai .NET, hãy đọc bài viết của tôi: http://blog.gauffin.org/2012/10/griffin-decoupling-the-queries/


Không phải là mẫu thay thế Bạn đề xuất sẽ tạo ra sự lộn xộn hơn nữa đối với các dự án lớn hơn vì đây là phương thức nào trong Mẫu lưu trữ bây giờ là cả một lớp?
Dante

3
Nếu định nghĩa của bạn về việc có các lớp học nhỏ là lộn xộn, thì có. Trong trường hợp đó, bạn không nên cố gắng tuân theo các nguyên tắc RẮN vì việc áp dụng chúng sẽ luôn tạo ra nhiều lớp (nhỏ hơn).
jgauffin

2
Các lớp nhỏ hơn sẽ tốt hơn, nhưng việc điều hướng của các lớp truy vấn hiện tại sẽ gây rắc rối hơn, hơn là nói, duyệt phần bên trong của kho lưu trữ (miền) để xem, nếu chức năng cần thiết nằm trong đó và nếu không, sẽ thực hiện nó.
Dante

4
Cấu trúc dự án trở nên quan trọng hơn. Nếu bạn đặt tất cả các truy vấn trong một không gian tên như YourProject.YourDomainModelName.Queriesnó sẽ dễ dàng điều hướng.
jgauffin

Một điều tôi thấy khó thực hiện theo cách tốt với CQS là các truy vấn phổ biến. Ví dụ: được cung cấp cho một người dùng, một truy vấn sẽ nhận được tất cả các sinh viên liên quan (lớp khác, con riêng, v.v.). Bây giờ một truy vấn khác cần nhận con cho một người dùng và sau đó thực hiện một số thao tác trên chúng - vì vậy truy vấn 2 có thể tận dụng logic chung trong truy vấn 1 nhưng không có cách nào đơn giản để thực hiện điều đó. Tôi chưa thể tìm thấy bất kỳ triển khai CQS nào tạo điều kiện thuận lợi cho việc này. Các kho lưu trữ giúp về vấn đề này rất nhiều. Không phải là một fan hâm mộ của một trong hai mẫu mà chỉ chia sẻ mất của tôi.
Ông bà

8

Đầu tiên tôi nghĩ rằng ORM đã đủ lớn để trừu tượng hóa cơ sở dữ liệu của bạn. Một số ORM cung cấp ràng buộc cho tất cả các cơ sở dữ liệu quan hệ phổ biến (NHibernate có các ràng buộc với MSSQL, Oracle, MySQL, PostTHER, v.v.) Vì vậy, việc xây dựng sự trừu tượng hóa mới trên nó dường như không mang lại lợi nhuận cho tôi. Ngoài ra, lập luận rằng bạn cần "trừu tượng hóa" ORM này là vô nghĩa.

Nếu bạn vẫn muốn xây dựng sự trừu tượng này, tôi sẽ đi ngược lại mẫu Kho lưu trữ. Nó là tuyệt vời trong thời đại của SQL thuần túy, nhưng nó khá rắc rối khi đối mặt với ORM hiện đại. Chủ yếu là vì bạn kết thúc việc triển khai lại hầu hết các tính năng ORM, như các thao tác CRUD và truy vấn.

Nếu tôi xây dựng các bản tóm tắt như vậy, tôi sẽ sử dụng các quy tắc / mẫu này

  • CQRS
  • Sử dụng càng nhiều càng tốt các tính năng ORM hiện có
  • Chỉ gói gọn logic và truy vấn phức tạp
  • Cố gắng để "hệ thống ống nước" của ORM ẩn bên trong một kiến ​​trúc

Trong triển khai cụ thể, tôi sẽ sử dụng trực tiếp các thao tác CRUD của ORM mà không cần bất kỳ gói nào. Tôi cũng sẽ làm các truy vấn đơn giản trực tiếp trong mã. Nhưng các truy vấn phức tạp sẽ được gói gọn trong các đối tượng của riêng họ. Để "ẩn" ORM, tôi sẽ cố gắng đưa ngữ cảnh dữ liệu trong suốt vào một đối tượng dịch vụ / UI và thực hiện tương tự với các đối tượng truy vấn.

Điều cuối cùng tôi muốn nói, là nhiều người sử dụng ORM mà không biết cách sử dụng nó và làm thế nào để kiếm "lợi nhuận" tốt nhất từ ​​nó. Mọi người đề nghị các kho lưu trữ thường là loại này.

Khi đọc khuyến nghị, tôi sẽ nói blog của Ayende , đặc biệt là bài viết này .


+1 "Điều cuối cùng tôi muốn nói, là nhiều người sử dụng ORM mà không biết cách sử dụng và làm thế nào để kiếm" lợi nhuận "tốt nhất từ ​​nó. Mọi người khuyên các kho lưu trữ thường là loại này"
Misters

1

Vấn đề với việc triển khai rò rỉ là bạn cần nhiều điều kiện lọc khác nhau.

Bạn có thể thu hẹp api này nếu bạn triển khai phương thức kho lưu trữ FindByExample và sử dụng nó như thế này

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);

0

Xem xét làm cho các tiện ích mở rộng của bạn hoạt động trên IQueryable thay vì IRep repository.

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

Để kiểm tra đơn vị:

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

Không có mocking giả cần thiết để thử nghiệm. Bạn cũng có thể kết hợp các phương thức mở rộng này để tạo các truy vấn phức tạp hơn khi cần.


5
-1 a) IQueryablelà một người mẹ tồi, hay đúng hơn là giao diện GOD. Việc triển khai nó là rất có và có rất ít (nếu có) hoàn thành các nhà cung cấp LINQ cho Sql. b) Các phương pháp mở rộng làm cho việc mở rộng (một trong những nguyên tắc OOP cơ bản) là không thể.
jgauffin

0

Tôi đã viết một mẫu đối tượng truy vấn khá gọn gàng cho NHibernate tại đây: https://github.com/shaynevanasperen/NHibernate.Simes.Operations

Nó hoạt động bằng cách sử dụng một giao diện như thế này:

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

Cho một lớp thực thể POCO như thế này:

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

Bạn có thể xây dựng các đối tượng truy vấn như thế này:

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

Và sau đó sử dụng chúng như thế này:

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });

1
Mặc dù liên kết này có thể trả lời câu hỏi, tốt hơn là bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi.
Dan Pichelman

Tôi xin lỗi, tôi đã vội. Trả lời cập nhật để hiển thị một số mã ví dụ.
Shayne

1
+1 để cải thiện bài đăng với nhiều nội dung hơn.
Songo
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.