Các chiến lược để tránh SQL trong Bộ điều khiển của bạn hoặc tôi nên có bao nhiêu phương thức trong Mô hình của mình?


17

Vì vậy, một tình huống tôi gặp phải thường xuyên là một trong những mô hình của tôi bắt đầu:

  • Phát triển thành quái vật với hàng tấn phương pháp

HOẶC LÀ

  • Cho phép bạn chuyển các phần SQL cho chúng, để chúng đủ linh hoạt để không yêu cầu hàng triệu phương thức khác nhau

Ví dụ: giả sử chúng ta có mô hình "widget". Chúng tôi bắt đầu với một số phương pháp cơ bản:

  • nhận ($ id)
  • chèn (bản ghi $)
  • cập nhật ($ id, $ record)
  • xóa ($ id)
  • getList () // lấy danh sách Widgets

Đó là tất cả tốt và đẹp, nhưng sau đó chúng tôi cần một số báo cáo:

  • listCreatedB between ($ start_date, $ end_date)
  • listPurchasingB between ($ start_date, $ end_date)
  • listOfPending ()

Và sau đó báo cáo bắt đầu trở nên phức tạp:

  • listPendingCreatedB between ($ start_date, $ end_date)
  • listForCustomer ($ customer_id)
  • listPendingCreatedB AmongForCustomer ($ customer_id, $ start_date, $ end_date)

Bạn có thể thấy nơi này đang phát triển ... cuối cùng chúng ta có rất nhiều yêu cầu truy vấn cụ thể mà tôi cần phải thực hiện hàng tấn phương thức hoặc một số đối tượng "truy vấn" mà tôi có thể chuyển đến một -> truy vấn (truy vấn $ truy vấn) phương thức ...

... hoặc chỉ cắn viên đạn, và bắt đầu làm một cái gì đó như thế này:

  • list = MyModel-> truy vấn ("start_date> X AND end_date <Y AND cấp phát = 1 AND customer_id = Z")

Có một sự hấp dẫn nhất định khi chỉ có một phương thức như vậy thay vì 50 triệu phương thức cụ thể khác ... nhưng đôi khi cảm thấy "sai" khi nhét một đống những gì về cơ bản SQL vào bộ điều khiển.

Có cách "đúng" nào để xử lý các tình huống như thế này không? Có vẻ chấp nhận được việc nhồi các truy vấn như thế vào một phương thức chung -> query () không?

Có chiến lược tốt hơn?


Tôi đang trải qua vấn đề tương tự ngay bây giờ trong một dự án không phải là MVC. Câu hỏi tiếp tục được đưa ra là lớp truy cập dữ liệu có nên trừu tượng hóa mọi thủ tục được lưu trữ và không để lại cơ sở dữ liệu của lớp logic nghiệp vụ, hay lớp truy cập dữ liệu phải chung chung, với chi phí của lớp nghiệp vụ có biết gì về cơ sở dữ liệu cơ sở không? Có lẽ một giải pháp trung gian là có một cái gì đó giống như các tham số ExecuteSP (chuỗi spName, params object []), sau đó bao gồm tất cả các tên SP trong tệp cấu hình cho lớp nghiệp vụ để đọc. Tôi thực sự không có một câu trả lời rất tốt cho điều này, mặc dù.
Greg Jackson

Câu trả lời:


10

Các mô hình kiến ​​trúc ứng dụng doanh nghiệp của Martin Fowler mô tả một số tài liệu liên quan đến ORM, bao gồm cả việc sử dụng Đối tượng truy vấn, đó là những gì tôi đề xuất.

Các đối tượng truy vấn cho phép bạn tuân theo nguyên tắc Trách nhiệm đơn lẻ, bằng cách tách logic cho từng truy vấn thành các đối tượng chiến lược được quản lý và duy trì riêng lẻ. Bộ điều khiển của bạn có thể quản lý việc sử dụng chúng trực tiếp hoặc ủy quyền điều đó cho bộ điều khiển phụ hoặc đối tượng trợ giúp.

Bạn sẽ có rất nhiều trong số họ? Chắc chắn rồi. Một số có thể được nhóm vào các truy vấn chung? Có một lần nữa.

Bạn có thể sử dụng phép nội xạ phụ thuộc để tạo các đối tượng từ siêu dữ liệu không? Đó là những gì hầu hết các công cụ ORM làm.


4

Không có cách chính xác để làm điều này. Nhiều người sử dụng ORM để trừu tượng hóa tất cả sự phức tạp. Một số ORM nâng cao hơn dịch các biểu thức mã thành các câu lệnh SQL phức tạp. Các ORM cũng có nhược điểm của chúng, tuy nhiên đối với nhiều ứng dụng, lợi ích vượt xa chi phí.

Nếu bạn không làm việc với một tập dữ liệu lớn, điều đơn giản nhất là chọn toàn bộ bảng vào bộ nhớ và lọc theo mã.

//pseudocode
List<Person> people = Sql.GetList<Person>("select * from people");
List<Person> over21 = people.Where(x => x.Age >= 21);

Đối với các ứng dụng báo cáo nội bộ phương pháp này có thể là tốt. Nếu tập dữ liệu thực sự lớn, bạn sẽ bắt đầu cần nhiều phương thức tùy chỉnh cũng như các chỉ mục phù hợp trên bảng của mình.


1
+ 1 cho "Không có cách chính xác để làm điều này"
ozz

1
Thật không may, lọc bên ngoài tập dữ liệu không thực sự là một tùy chọn với ngay cả những tập dữ liệu nhỏ nhất chúng tôi làm việc - nó quá chậm. :-( Thật tốt khi biết rằng những người khác cũng gặp phải vấn đề tương tự của tôi .
Keith Palmer Jr.

@KeithPalmer vì tò mò, bàn của bạn lớn cỡ nào?
dan

Hàng trăm ngàn hàng, nếu không muốn nói là nhiều hơn. Quá nhiều để lọc với hiệu suất chấp nhận được bên ngoài cơ sở dữ liệu, ĐẶC BIỆT với kiến ​​trúc phân tán trong đó các cơ sở dữ liệu không nằm trên cùng một máy với ứng dụng.
Keith Palmer Jr.

-1 cho "Không có cách chính xác để làm điều này". Có một số cách chính xác. Nhân đôi số phương thức khi bạn thêm một tính năng như OP đang thực hiện là một cách tiếp cận không thể đánh giá được và phương pháp thay thế được đề xuất ở đây cũng không thể đánh giá được, chỉ liên quan đến kích thước cơ sở dữ liệu thay vì số lượng tính năng truy vấn. Phương pháp mở rộng có thể tồn tại, xem các câu trả lời khác.
Theodore Murdock

4

Một số ORM cho phép bạn xây dựng các truy vấn phức tạp bắt đầu từ các phương thức cơ bản. Ví dụ

old_purchases = (Purchase.objects
    .filter(date__lt=date.today(),type=Purchase.PRESENT).
    .excude(status=Purchase.REJECTED)
    .order_by('customer'))

là một truy vấn hoàn toàn hợp lệ trong Django ORM .

Ý tưởng là bạn có một số trình tạo truy vấn (trong trường hợp này Purchase.objects) có trạng thái bên trong thể hiện thông tin về một truy vấn. Các phương pháp như get, filter, exclude, order_bycó giá trị và trả về một người xây dựng truy vấn mới với một tình trạng cập nhật. Các đối tượng này triển khai một giao diện có thể lặp lại, do đó khi bạn lặp lại chúng, truy vấn được thực hiện và bạn nhận được kết quả của truy vấn được xây dựng cho đến nay. Mặc dù ví dụ này được lấy từ Django, bạn sẽ thấy cấu trúc tương tự trong nhiều ORM khác.


Tôi không thấy lợi thế này có gì so với old_purchase = Purchasing.query ("date> date.today () AND type = Purchasing.PRESENT AND status! = Purchasing.RE DỰED"); Bạn sẽ không giảm độ phức tạp hoặc trừu tượng hóa bất cứ điều gì bằng cách chỉ tạo các AND và OR của SQL thành các phương thức AND và OR - bạn chỉ đang thay đổi cách biểu diễn của AND và OR, phải không?
Keith Palmer Jr.

4
Thật ra là không. Bạn đang trừu tượng hóa SQL, mua cho bạn rất nhiều lợi thế. Đầu tiên, bạn tránh tiêm. Sau đó, bạn có thể thay đổi cơ sở dữ liệu cơ bản mà không phải lo lắng về các phiên bản hơi khác nhau của phương ngữ SQL, vì ORM xử lý việc này cho bạn. Trong nhiều trường hợp, bạn cũng có thể đặt một phụ trợ NoQuery mà không nhận thấy. Thứ ba, các trình xây dựng truy vấn này là các đối tượng mà bạn có thể vượt qua như mọi thứ khác. Điều này có nghĩa là mô hình của bạn có thể tạo một nửa truy vấn (ví dụ: bạn có thể có một số phương thức cho các trường hợp phổ biến nhất) và sau đó nó có thể được tinh chỉnh trong bộ điều khiển để xử lý ..
Andrea

2
... trường hợp cụ thể nhất. Một ví dụ điển hình là xác định thứ tự mặc định cho các mô hình trong Django. Tất cả các kết quả truy vấn sẽ tuân theo thứ tự đó trừ khi bạn chỉ định khác. Thứ tư, nếu bạn cần phải chuẩn hóa dữ liệu của mình vì lý do hiệu suất, bạn chỉ phải điều chỉnh ORM thay vì viết lại tất cả các truy vấn của mình.
Andrea

+1 Đối với các ngôn ngữ truy vấn động như ngôn ngữ được đề cập và LINQ.
Evan Plaice

2

Có cách tiếp cận thứ ba.

Ví dụ cụ thể của bạn thể hiện sự tăng trưởng theo cấp số nhân về số lượng phương thức cần thiết khi số lượng tính năng cần thiết tăng lên: chúng tôi muốn khả năng cung cấp các truy vấn nâng cao, kết hợp mọi tính năng truy vấn ... nếu chúng tôi làm điều đó bằng cách thêm phương thức, chúng tôi có một phương thức cho truy vấn cơ bản, hai nếu chúng ta thêm một tính năng tùy chọn, bốn nếu chúng ta thêm hai, tám nếu chúng ta thêm ba, 2 ^ n nếu chúng ta thêm n tính năng.

Đó rõ ràng là không thể nhầm lẫn ngoài ba hoặc bốn tính năng, và có mùi khó chịu của rất nhiều mã liên quan chặt chẽ gần như được sao chép giữa các phương thức.

Bạn có thể tránh điều đó bằng cách thêm một đối tượng dữ liệu để giữ các tham số và có một phương thức duy nhất xây dựng truy vấn dựa trên tập các tham số được cung cấp (hoặc không được cung cấp). Trong trường hợp đó, việc thêm một tính năng mới, chẳng hạn như phạm vi ngày cũng đơn giản như thêm setters và getters cho phạm vi ngày vào đối tượng dữ liệu của bạn, sau đó thêm một chút mã nơi xây dựng truy vấn được tham số hóa:

if (dataObject.getStartDate() != null) {
    query += " AND (date BETWEEN ? AND ?) "
}

... và nơi các tham số được thêm vào truy vấn:

if (dataObject.getStartDate() != null) {
    preparedStatement.setTime(dataObject.getStartDate());
    preparedStatement.setTime(dataObject.getEndDate());
}

Cách tiếp cận này cho phép tăng trưởng mã tuyến tính khi các tính năng được thêm vào, mà không phải cho phép các truy vấn tùy ý, không tham số.


0

Tôi nghĩ rằng sự đồng thuận chung là giữ càng nhiều quyền truy cập dữ liệu càng tốt trong các mô hình của bạn trong MVC. Một trong những nguyên tắc thiết kế khác là chuyển một số truy vấn chung hơn của bạn (Những truy vấn không liên quan trực tiếp đến mô hình của bạn) sang mức cao hơn, trừu tượng hơn, nơi bạn cũng có thể cho phép sử dụng nó cho các mô hình khác. (Trong RoR, chúng tôi có một thứ gọi là khung) Ngoài ra còn có một thứ khác mà bạn phải xem xét và đó là tính bảo trì của mã của bạn. Khi dự án của bạn phát triển, nếu bạn có quyền truy cập dữ liệu trong bộ điều khiển, việc theo dõi nó sẽ ngày càng khó khăn hơn (Chúng tôi hiện đang phải đối mặt với vấn đề này trong một dự án lớn) Các mô hình, mặc dù lộn xộn với các phương thức cung cấp một điểm liên lạc duy nhất cho bất kỳ bộ điều khiển nào có thể kết thúc quering từ các bảng. (Điều này cũng có thể dẫn đến việc sử dụng lại mã lần lượt có lợi)


1
Ví dụ về những gì bạn đang nói về ...?
Keith Palmer Jr.

0

Giao diện lớp dịch vụ của bạn có thể có nhiều phương thức, nhưng lệnh gọi đến cơ sở dữ liệu có thể chỉ có một phương thức.

Một cơ sở dữ liệu có 4 hoạt động chính

  • Chèn
  • Cập nhật
  • Xóa bỏ
  • Truy vấn

Một phương pháp tùy chọn khác có thể là thực thi một số thao tác cơ sở dữ liệu không thuộc các hoạt động DB cơ bản. Hãy gọi đó là Thực thi.

Chèn và Cập nhật có thể được kết hợp thành một thao tác, được gọi là Lưu.

Rất nhiều phương pháp của bạn là truy vấn. Vì vậy, bạn có thể tạo một giao diện chung để đáp ứng hầu hết các nhu cầu trước mắt. Đây là một giao diện chung mẫu:

 public interface IDALService
    {
        DataTransferObject<T> Save<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Search<T>(DataTransferObject<T> Dto) where T: IPOCO;
        DataTransferObject<T> Delete<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Execute<T>(DataTransferObject<T> Dto) where T : IPOCO;
    }

Đối tượng truyền dữ liệu là chung chung và sẽ có tất cả các bộ lọc, tham số, sắp xếp, v.v. của bạn có trong đó. Lớp dữ liệu sẽ chịu trách nhiệm phân tích và trích xuất phần này và thiết lập hoạt động cho cơ sở dữ liệu thông qua các thủ tục được lưu trữ, sql tham số, linq, v.v. Vì vậy, SQL không được truyền giữa các lớp. Đây thường là những gì ORM làm, nhưng bạn có thể tự cuộn và có bản đồ của riêng mình.

Vì vậy, trong trường hợp của bạn, bạn có Widgets. Widgets sẽ thực hiện giao diện IPOCO.

Vì vậy, trong mô hình lớp dịch vụ của bạn sẽ có getList().

Sẽ cần một lớp ánh xạ để xử lý tranforming getListvào

Search<Widget>(DataTransferObject<Widget> Dto)

và ngược lại. Như những người khác đã đề cập, đôi khi điều này được thực hiện thông qua ORM, nhưng cuối cùng bạn kết thúc với rất nhiều loại mã soạn sẵn, đặc biệt nếu bạn có 100 bảng. ORM kỳ diệu tạo ra SQL tham số hóa và chạy nó dựa trên cơ sở dữ liệu. Nếu tự lăn, ngoài ra trong lớp dữ liệu, người lập bản đồ sẽ cần thiết để thiết lập SP, linq, v.v. (Về cơ bản là sql đi đến cơ sở dữ liệu).

Như đã đề cập trước đó, DTO là một đối tượng được tạo thành bởi thành phần. Có lẽ một trong những đối tượng chứa trong nó là một đối tượng được gọi là QueryParameter. Đây sẽ là tất cả các tham số cho truy vấn sẽ được thiết lập và sử dụng bởi truy vấn. Một đối tượng khác sẽ là Danh sách các đối tượng được trả về từ các truy vấn, cập nhật, ext. Đây là tải trọng. Nếu trong trường hợp này, tải trọng sẽ là Danh sách các vật dụng.

Vì vậy, chiến lược cơ bản là:

  • Các cuộc gọi lớp dịch vụ
  • Chuyển đổi cuộc gọi lớp dịch vụ sang cơ sở dữ liệu bằng cách sử dụng một số loại Kho lưu trữ / Ánh xạ
  • Cuộc gọi cơ sở dữ liệu

Trong trường hợp của bạn, tôi nghĩ rằng mô hình có thể có nhiều phương thức, nhưng tối ưu bạn muốn cuộc gọi cơ sở dữ liệu là chung chung. Bạn vẫn kết thúc với rất nhiều mã ánh xạ soạn sẵn (đặc biệt là với SP) hoặc mã ORM ma thuật đang tự động tạo SQL tham số cho bạn.

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.