这篇文章展示了如何查询高度规范化的SQL数据库,以及如何将结果映射到一组高度嵌套的C#POCO对象中。
配料:
- 8行C#。
- 一些使用某些联接的相当简单的SQL。
- 两个很棒的库。
这让我解决这个问题的见解是分开MicroORM
的mapping the result back to the POCO Entities
。因此,我们使用两个单独的库:
本质上,我们使用Dapper查询数据库,然后使用Slapper.Automapper 将结果直接映射到我们的POCO中。
优点
- 简单性。它少于8行代码。我发现这更容易理解,调试和更改。
- 更少的代码。Slapper.Automapper只需编写几行代码,即使我们有一个复杂的嵌套POCO(即POCO包含
List<MyClass1>
反过来包含List<MySubClass2>
等),Automapper也需要处理您向它扔的任何内容。
- 速度。这两个库都进行了大量的优化和缓存,以使其运行速度几乎与手动调整的ADO.NET查询一样快。
- 关注点分离。我们可以将MicroORM更改为另一种,映射仍然有效,反之亦然。
- 灵活性强。Slapper.Automapper处理任意嵌套的层次结构,它不仅限于几个嵌套级别。我们可以轻松进行快速更改,并且一切仍然可以进行。
- 调试。我们首先可以看到SQL查询运行正常,然后可以检查SQL查询结果是否正确映射回目标POCO实体。
- 易于SQL开发。我发现创建扁平化查询
inner joins
以返回扁平化结果要比创建多个select语句(在客户端进行拼接)容易得多。
- SQL中的优化查询。在高度规范化的数据库中,创建平面查询使SQL引擎可以对整体应用高级优化,如果构造并运行了许多小的单个查询,通常这是不可能的。
- 信任。Dapper是StackOverflow的后端,而且,Randy Burden有点像超级巨星。我还要说什么吗?
- 发展速度。我能够执行很多嵌套的异常复杂的查询,并且开发时间很短。
- 更少的错误。我曾经写过它,但是它确实起作用了,这种技术现在正在帮助为FTSE公司提供动力。几乎没有代码,没有意外行为。
缺点
- 返回超过1,000,000行的规模。返回<100,000行时效果很好。但是,如果我们要带回> 1,000,000行,为了减少我们和SQL Server之间的通信量,我们不应该使用
inner join
(将带回来的重复项)弄平它,而应该使用多个select
语句并将所有内容缝合在一起客户端(请参阅此页面上的其他答案)。
- 此技术是面向查询的。我还没有使用这种技术来写数据库,但是我敢肯定Dapper可以完成更多的工作,因为StackOverflow本身使用Dapper作为其数据访问层(DAL)。
性能测试
在我的测试中,Slapper.Automapper在Dapper返回的结果中增加了很小的开销,这意味着它仍然比Entity Framework快10倍,并且组合仍然相当接近SQL + C#能够达到的理论最大速度。
在大多数实际情况下,大部分开销将出现在非最佳SQL查询中,而不是在C#端对结果进行一些映射。
性能测试结果
迭代总数:1000
Dapper by itself
:每个查询1.889毫秒(使用)3 lines of code to return the dynamic
。
Dapper + Slapper.Automapper
:每个查询2.463毫秒,使用额外的3 lines of code for the query + mapping from dynamic to POCO Entities
。
工作实例
在此示例中,我们具有的列表Contacts
,并且每个列表Contact
可以具有一个或多个phone numbers
。
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; }
}
SQL表 TestContact
SQL表 TestPhone
请注意,此表有一个外键ContactID
,该外键引用该TestContact
表(与List<TestPhone>
上面的POCO中的相对应)。
产生固定结果的SQL
在我们的SQL查询中,我们JOIN
以平整,非规范化的形式使用尽可能多的语句来获取所需的所有数据。是的,这可能会在输出中产生重复项,但是当我们使用Slapper.Automapper将查询结果直接直接映射到POCO对象映射中时,这些重复项将被自动消除。
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
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);
}
}
}
}
输出量
POCO实体层次结构
在Visual Studio中查看,我们可以看到Slapper.Automapper已正确填充了POCO实体,即,我们有个List<TestContact>
,每个TestContact
都有一个List<TestPhone>
。
笔记
Dapper和Slapper.Automapper都在内部缓存所有内容以提高速度。如果遇到内存问题(极不可能),请确保偶尔清除两者的缓存。
确保使用下划线(_
)表示法为Slapper.Automapper提供有关如何将结果映射到POCO实体的线索,从而命名返回的列。
确保在每个POCO实体的主键上提供Slapper.Automapper线索(请参阅第几行Slapper.AutoMapper.Configuration.AddIdentifiers
)。您也可以Attributes
在POCO上使用。如果跳过此步骤,则从理论上讲可能会出错,因为Slapper.Automapper将不知道如何正确执行映射。
更新2015-06-14
成功地将此技术应用于具有40多个标准化表的庞大生产数据库。它的工作完美地与先进的SQL查询超过16映射inner join
和left join
到适当的POCO层次(含4层的嵌套)。查询的速度非常快,几乎与在ADO.NET中手动编码的速度一样快(查询通常为52毫秒,从平面结果到POCO层次结构的映射通常为50毫秒)。这确实没有什么革命性的,但是它确实在速度和易用性方面优于Entity Framework,尤其是如果我们正在做的是运行查询。
更新2016-02-19
代码在生产中完美运行了9个月。最新的版本Slapper.Automapper
具有我为解决与SQL查询中返回的null有关的问题而应用的所有更改。
更新2017-02-20
代码在生产中完美运行了21个月,并且已经处理了FTSE 250公司中数百名用户的连续查询。
Slapper.Automapper
将.csv文件直接映射到POCO列表中也非常有用。将.csv文件读取到IDictionary列表中,然后将其直接映射到POCO的目标列表中。唯一的技巧是必须添加一个属性int Id {get; set}
,并确保它对于每一行都是唯一的(否则自动映射器将无法区分行)。
更新2019-01-29
较小的更新以添加更多代码注释。
参见:https : //github.com/SlapperAutoMapper/Slapper.AutoMapper