Bài đăng này chỉ ra cách truy vấn cơ sở dữ liệu SQL được chuẩn hóa cao và ánh xạ kết quả thành một tập hợp các đối tượng C # POCO lồng nhau cao.
Thành phần:
- 8 dòng C #.
- Một số SQL đơn giản hợp lý sử dụng một số phép nối.
- Hai thư viện tuyệt vời.
Cái nhìn sâu sắc cho phép tôi giải quyết vấn đề này là tách biệt MicroORM
khỏi mapping the result back to the POCO Entities
. Do đó, chúng tôi sử dụng hai thư viện riêng biệt:
Về cơ bản, chúng tôi sử dụng Dapper để truy vấn cơ sở dữ liệu, sau đó sử dụng Slapper.Automapper để ánh xạ kết quả thẳng vào POCO của chúng tôi.
Ưu điểm
- Sự đơn giản . Ít hơn 8 dòng mã của nó. Tôi thấy điều này dễ hiểu hơn, gỡ lỗi và thay đổi.
- Ít mã hơn . Một vài dòng mã là tất cả Slapper.Automapper cần để xử lý bất cứ điều gì bạn ném vào nó, ngay cả khi chúng tôi có một POCO phức tạp lồng nhau (ví dụ POCO chứa
List<MyClass1>
mà lần lượt chứa List<MySubClass2>
, vv).
- Tốc độ . Cả hai thư viện này đều có khả năng tối ưu hóa và bộ nhớ đệm đặc biệt để làm cho chúng chạy nhanh như các truy vấn ADO.NET được điều chỉnh thủ công.
- Tách mối quan tâm . Chúng ta có thể thay đổi MicroORM cho một MicroORM khác và ánh xạ vẫn hoạt động và ngược lại.
- Tính linh hoạt . Slapper.Automapper xử lý các cấu trúc phân cấp lồng nhau tùy ý, nó không giới hạn ở một vài cấp độ lồng nhau. Chúng tôi có thể dễ dàng thực hiện các thay đổi nhanh chóng và mọi thứ vẫn sẽ hoạt động.
- Gỡ lỗi . Trước tiên, chúng ta có thể thấy rằng truy vấn SQL đang hoạt động bình thường, sau đó chúng ta có thể kiểm tra xem kết quả truy vấn SQL có được ánh xạ trở lại các Thực thể POCO đích đúng cách hay không.
- Dễ dàng phát triển trong SQL . Tôi thấy rằng việc tạo các truy vấn phẳng
inner joins
để trả về các kết quả phẳng dễ dàng hơn nhiều so với việc tạo nhiều câu lệnh lựa chọn, với khâu ở phía máy khách.
- Các truy vấn được tối ưu hóa trong SQL . Trong cơ sở dữ liệu được chuẩn hóa cao, việc tạo một truy vấn phẳng cho phép công cụ SQL áp dụng các tối ưu hóa nâng cao cho toàn bộ cơ sở dữ liệu mà thông thường sẽ không thể thực hiện được nếu nhiều truy vấn riêng lẻ nhỏ được tạo và chạy.
- Tin cậy . Dapper là hậu thuẫn cho StackOverflow, và, tốt, Randy Burden là một siêu sao. Tôi có cần nói gì thêm không?
- Tốc độ phát triển. Tôi đã có thể thực hiện một số truy vấn cực kỳ phức tạp, với nhiều cấp độ lồng ghép và thời gian dành cho nhà phát triển khá thấp.
- Ít lỗi hơn. Tôi đã viết nó một lần, nó chỉ hoạt động và kỹ thuật này hiện đang giúp cung cấp năng lượng cho một công ty FTSE. Có rất ít mã nên không có hành vi bất ngờ.
Nhược điểm
- Đã trả lại tỷ lệ vượt quá 1.000.000 hàng. Hoạt động tốt khi trả về <100.000 hàng. Tuy nhiên, nếu chúng ta đưa trở lại hơn 1.000.000 hàng, để giảm lưu lượng truy cập giữa chúng ta và máy chủ SQL, chúng ta không nên san bằng nó bằng cách sử dụng
inner join
(điều này mang lại các bản sao), thay vào đó chúng ta nên sử dụng nhiều select
câu lệnh và ghép mọi thứ lại với nhau trên phía khách hàng (xem các câu trả lời khác trên trang này).
- Kỹ thuật này là định hướng truy vấn . Tôi chưa sử dụng kỹ thuật này để ghi vào cơ sở dữ liệu, nhưng tôi chắc chắn rằng Dapper có nhiều khả năng làm điều này với một số công việc bổ sung, vì bản thân StackOverflow sử dụng Dapper làm Lớp truy cập dữ liệu (DAL).
Kiểm tra năng suất
Trong các thử nghiệm của tôi, Slapper.Automapper đã thêm một chi phí nhỏ vào kết quả do Dapper trả về, có nghĩa là nó vẫn nhanh hơn 10 lần so với Entity Framework và sự kết hợp này vẫn khá gần với tốc độ tối đa lý thuyết mà SQL + C # có thể có .
Trong hầu hết các trường hợp thực tế, phần lớn chi phí sẽ nằm trong một truy vấn SQL kém tối ưu và không phải với một số ánh xạ kết quả ở phía C #.
Kết quả kiểm tra hiệu suất
Tổng số lần lặp: 1000
Dapper by itself
: 1,889 mili giây mỗi truy vấn, sử dụng 3 lines of code to return the dynamic
.
Dapper + Slapper.Automapper
: 2,463 mili giây cho mỗi truy vấn, sử dụng thêm 3 lines of code for the query + mapping from dynamic to POCO Entities
.
Ví dụ về công việc
Trong ví dụ này, chúng tôi có danh sách Contacts
và mỗi danh sách Contact
có thể có một hoặc nhiều phone numbers
.
Thực thể POCO
public class TestContact
{
public int ContactID { get; set; }
public string ContactName { get; set; }
public List<TestPhone> TestPhones { get; set; }
}
public class TestPhone
{
public int PhoneId { get; set; }
public int ContactID { get; set; }
public string Number { get; set; }
}
Bảng SQL TestContact
Bảng SQL TestPhone
Lưu ý rằng bảng này có một khóa ngoại ContactID
tham chiếu đến TestContact
bảng (điều này tương ứng với List<TestPhone>
trong POCO ở trên).
SQL tạo ra kết quả phẳng
Trong truy vấn SQL của chúng tôi, chúng tôi sử dụng bao nhiêu JOIN
câu lệnh mà chúng tôi cần để nhận được tất cả dữ liệu chúng tôi cần, ở dạng phẳng, không chuẩn hóa . Có, điều này có thể tạo ra các bản sao trong đầu ra, nhưng các bản sao này sẽ tự động bị loại bỏ khi chúng ta sử dụng Slapper.Automapper để tự động ánh xạ kết quả của truy vấn này thẳng vào bản đồ đối tượng POCO của chúng ta.
USE [MyDatabase];
SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId
Mã C #
const string sql = @"SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId";
string connectionString =
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
{
dynamic test = conn.Query<dynamic>(sql);
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestContact), new List<string> { "ContactID" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestPhone), new List<string> { "PhoneID" });
var testContact = (Slapper.AutoMapper.MapDynamic<TestContact>(test) as IEnumerable<TestContact>).ToList();
foreach (var c in testContact)
{
foreach (var p in c.TestPhones)
{
Console.Write("ContactName: {0}: Phone: {1}\n", c.ContactName, p.Number);
}
}
}
}
Đầu ra
Hệ thống phân cấp thực thể POCO
Nhìn trong Visual Studio, chúng ta có thể thấy rằng Slapper.Automapper đã điền đúng các Thực thể POCO của chúng tôi, tức là chúng tôi có một List<TestContact>
và mỗi thực thể TestContact
có một List<TestPhone>
.
Ghi chú
Cả Dapper và Slapper.Automapper đều lưu trữ mọi thứ bên trong để tăng tốc độ. Nếu bạn gặp sự cố bộ nhớ (rất khó xảy ra), hãy đảm bảo rằng bạn thỉnh thoảng xóa bộ nhớ cache cho cả hai vấn đề.
Đảm bảo rằng bạn đặt tên cho các cột quay trở lại, sử dụng ký hiệu gạch dưới ( _
) để cung cấp cho Slapper.Automapper manh mối về cách ánh xạ kết quả vào các Thực thể POCO.
Đảm bảo rằng bạn cung cấp cho Slapper.Automapper manh mối về khóa chính cho mỗi Thực thể POCO (xem các dòng Slapper.AutoMapper.Configuration.AddIdentifiers
). Bạn cũng có thể sử dụng Attributes
POCO cho việc này. Nếu bạn bỏ qua bước này, thì nó có thể bị sai (về lý thuyết), vì Slapper.Automapper sẽ không biết cách thực hiện ánh xạ đúng cách.
Cập nhật 2015-06-14
Đã áp dụng thành công kỹ thuật này vào cơ sở dữ liệu sản xuất khổng lồ với hơn 40 bảng chuẩn hóa. Nó hoạt động hoàn hảo để ánh xạ một truy vấn SQL nâng cao với hơn 16 inner join
và left join
vào hệ thống phân cấp POCO thích hợp (với 4 cấp độ lồng nhau). Các truy vấn nhanh đến chóng mặt, gần như nhanh bằng cách viết tay nó trong ADO.NET (thường là 52 mili giây cho truy vấn và 50 mili giây để ánh xạ từ kết quả phẳng vào hệ thống phân cấp POCO). Điều này thực sự không có gì mang tính cách mạng, nhưng nó chắc chắn đánh bại Entity Framework về tốc độ và tính dễ sử dụng, đặc biệt nếu tất cả những gì chúng ta đang làm là chạy các truy vấn.
Cập nhật 2016-02-19
Code đã chạy hoàn hảo trong quá trình sản xuất trong 9 tháng. Phiên bản mới nhất của Slapper.Automapper
có tất cả các thay đổi mà tôi đã áp dụng để khắc phục sự cố liên quan đến giá trị rỗng được trả về trong truy vấn SQL.
Cập nhật 2017-02-20
Mã đã chạy hoàn hảo trong quá trình sản xuất trong 21 tháng và đã xử lý các truy vấn liên tục từ hàng trăm người dùng trong một công ty FTSE 250.
Slapper.Automapper
cũng rất tốt để ánh xạ thẳng tệp .csv vào danh sách POCO. Đọc tệp .csv thành danh sách IDictionary, sau đó ánh xạ thẳng vào danh sách POCO đích. Mẹo duy nhất là bạn phải thêm một propery int Id {get; set}
và đảm bảo rằng nó là duy nhất cho mọi hàng (nếu không, trình tự động sẽ không thể phân biệt giữa các hàng).
Cập nhật 2019-01-29
Cập nhật nhỏ để thêm nhiều bình luận mã hơn.
Xem: https://github.com/SlapperAutoMapper/Slapper.AutoMapper