萍乡网站建设,网页界面设计以什么为载体,成都知名网站建设公司,jsp和.net做网站的区别关键要点#xff1a; Dapper这类微ORM#xff08;Micro-ORM#xff09;虽然提供了最好的性能#xff0c;但也需要去做最多的工作。在无需复杂对象图时#xff0c;Chain这类Fluent ORM更易于使用。对实体框架#xff08;Entity Framework#xff09;做大量的工作后#… 关键要点 Dapper这类微ORMMicro-ORM虽然提供了最好的性能但也需要去做最多的工作。在无需复杂对象图时Chain这类Fluent ORM更易于使用。对实体框架Entity Framework做大量的工作后其性能可显著提高。为获得数据库的最大性能需要采用可能会有些繁琐的投影Projection操作。ORM整体上的局部更新可能会存在问题。 在现代企业开发中可采用多种方法构建数据存取层data access layer DAL。使用C#做开发时DAL的最底层几乎总是使用ADO.NET。但这时常会形成一个笨重的库所以通常会在DAL的底层之上再部署一个ORM层。为允许模拟和隐藏ORM的细节整个DAL包装在存储内。 在这一系列的文章中我们将审视三种使用不同类型ORM构建仓储模式的方法分别是 实体框架一种传统的“全特性”或“OOP”类型的ORM。Dapper一种主要专注结果集映射的轻量级微ORM。Tortuga Chain一种基于函数式编程理念的Fluent ORM。 本文将侧重于开发人员可在典型仓储中用到的那些基本功能。在本系列文章的第二部分我们将着眼于那些开发人员基于实际情况而实现的高级技术。 插入Insert操作 对于任何CRUD操作集通常会首先实现基本的插入操作进而可用插入操作对其它的操作进行测试。 Chain Chain使用列名和属性名间的运行时匹配。对于在数据库中并不存在的对象除非启用了严格模式strict model否则将忽略该对象上的属性。类似地没有匹配属性的列不能成为生成SQL的组成部分。 public int Insert(Employee employee)
{return m_DataSource.Insert(HR.Employee, employee).ToInt32().Execute();
} Dapper 没有第三方扩展时Dapper需要编程人员手工指定所需的SQL其中包括了特定于数据库的逻辑用于返回新创建的主键。 public int Insert(Employee employee){const string sql INSERT INTO HR.Employee(FirstName,MiddleName,LastName,Title,ManagerKey,OfficePhone,CellPhone)
VALUES (FirstName,MiddleName,LastName,Title,ManagerKey,OfficePhone,CellPhone);SELECT SCOPE_IDENTITY()
;using (var con new SqlConnection(m_ConnectionString)){con.Open();return con.ExecuteScalarint(sql, employee);}} 实体框架 实体框架使用编译阶段映射在运行时生成SQL。需将任何没有匹配列的属性标记为NotMapped否则将会产生错误。 public int Insert(Employee employee){using (var context new CodeFirstModels()){context.Employees.Add(employee);context.SaveChanges();return employee.EmployeeKey;}} 更新Update操作 Chain Chain缺省使用数据库中所定义的主键。但是在设置了适当的插入选项后它将在模型中使用Key属性。 public void Update(Employee employee){m_DataSource.Update(HR.Employee, employee).Execute();} Dapper 与插入操作一样纯Dapper需用户手工编写必要的SQL语句。 public void Update(Employee employee){const string sql UPDATE HR.EmployeeSET FirstName FirstName,MiddleName MiddleName,LastName LastName,Title Title,ManagerKey ManagerKey,OfficePhone OfficePhone,CellPhone CellPhoneWHERE EmployeeKey EmployeeKey;using (var con new SqlConnection(m_ConnectionString)){con.Open();con.Execute(sql, employee);}} 实体框架初学者 实体框架为UPDATE语句查找Key属性以生成WHERE语句。 public void Update(Employee employee){using (var context new CodeFirstModels()){var entity context.Employees. Where(e e.EmployeeKey employee.EmployeeKey).First();entity.CellPhone employee.CellPhone;entity.FirstName employee.FirstName;entity.LastName employee.LastName;entity.ManagerKey employee.ManagerKey;entity.MiddleName employee.MiddleName;entity.OfficePhone employee.OfficePhone;entity.Title employee.Title;context.SaveChanges();}} 实体框架中级用户 使用实体框架时初学者常会在执行更新操作上犯错误。将实体添加到上下文中很容易就能实现它而这种模式应成为中级使用者的常识。这里给出使用实体状态“Modified”修正后的例子。 public void Update(Employee employee){using (var context new CodeFirstModels()){context.Entry(employee).State EntityState.Modified;context.SaveChanges();}} 读取全部Read All操作 读取全部操作在实体框架和Chain中是十分相似的不同之处在于在实体框架中实现需要编写更多行的代码而在Chain中实现需要编写更长的代码行。 Dapper当然是最为繁琐的因为它需要未经加工的SQL语句。即使如此仍可以通过使用SELECT *语句替代手工地指定列名而在一定程度上降低Dapper的开销。这在存在返回额外数据的风险的情况下降低了出现类与SQL语句不匹配的可能性。 Chain 在Chain中ToObject连接生成一系列所需的列。通过匹配所需列表与可用列的列表From连接生成SQL语句。 public IListEmployee GetAll()
{return m_DataSource.From(HR.Employee).ToCollectionEmployee().Execute();
} Dapper Dapper是最为繁琐的因为它需要原始未经加工的SQL语句。虽然这令人皱眉头但仍可以通过使用SELECT *语句替代手工地指定列名而在一定程度上降低Dapper的开销这样是不太可能漏掉列的虽然存在返回额外数据的风险。 public IListEmployee GetAll(){using (var con new SqlConnection(m_ConnectionString)){con.Open();return con.QueryEmployee( SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.ManagerKey, e.OfficePhone, e.CellPhone, e.CreatedDate FROM HR.Employee e).AsList();}} 实体框架 像以前一样实体框架使用编译期信息确定如何生成SQL语句。 public IListEmployee GetAll(){using (var context new CodeFirstModels()){return context.Employees.ToList();}} 按标识符获取Get by Id操作 需要注意的是随每个例子的语法稍作修改就可表明只返回一个对象。同样的基本过滤技术可用于返回多个对象。 Chain Chain严重依赖于“过滤对象”。这些对象直接被转义成参数化的WHERE语句语句中的每个属性间具有“AND”操作符。 public Employee Get(int employeeKey){return m_DataSource.From(HR.Employee, new { EmployeeKey employeeKey }).ToObjectEmployee().Execute();} Chain也允许用参数化的字符串表示WHERE语句虽然这个功能很少被用到。 如果主键是标量即主键中只有一列那么可使用简化的语法。 public Employee Get(int employeeKey){return m_DataSource.GetByKey(HR.Employee, employeeKey) .ToObjectEmployee().Execute();} Dapper 下例中可以看到Dapper手工指定了SQL语句。该语句与Chain和实体框架所生成的SQL语句在本质上是一致的。 using (var con new SqlConnection(m_ConnectionString)){con.Open();return con.QueryEmployee(SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.ManagerKey, e.OfficePhone, e.CellPhone, e.CreatedDate FROM HR.Employee e WHERE e.EmployeeKey EmployeeKey, new { EmployeeKey employeeKey }).First();} 实体框架 实体框架将表名和首个ToList或First操作间的所有内容看作为一个表达式树。在运行时评估该树以生成SQL语句。 public Employee Get(int employeeKey){using (var context new CodeFirstModels()){return context.Employees.Where(e e.EmployeeKey employeeKey).First();}} 删除Delete操作 Chain Chain期待包括主键的参数对象。而参数对象中的其它特性将被忽略该语法不支持批量删除。 public void Delete(int employeeKey){m_DataSource.Delete(HR.Employee, new { EmployeeKey employeeKey }).Execute();} 如果有标量主键可使用简化的语法。 public void Delete(int employeeKey){m_DataSource.DeleteByKey(HR.Employee, employeeKey).Execute();} Dapper public void Delete(int employeeKey){using (var con new SqlConnection(m_ConnectionString)){con.Open();con.Execute(DELETE FROM HR.Employee WHERE EmployeeKey EmployeeKey, new { EmployeeKey employeeKey });}} 实体框架初学者 初学者一般会取回一个记录然后迅速删除丢弃所有返回的信息。 public void Delete(int employeeKey){using (var context new CodeFirstModels()){var employee context.Employees. Where(e e.EmployeeKey employeeKey).First();context.Employees.Remove(employee);context.SaveChanges();}} 实体框架中级用户 可使用内嵌SQL避免数据库的往返交互操作。 public void Delete(int employeeKey){using (var context new CodeFirstModels()){context.Database.ExecuteSqlCommand( DELETE FROM HR.Employee WHERE EmployeeKey p0, employeeKey);}} 投影Projection操作 投影是中间层开发中的一个重要部分。在取回了比实际所需更多的数据时数据库常会完全失去使用覆盖索引或索引的能力这将导致严重的性能影响。 Chain 同上Chain将仅选取指定对象类型所需的所有列。 public IListEmployeeOfficePhone GetOfficePhoneNumbers(){return m_DataSource.From(HR.Employee). ToCollectionEmployeeOfficePhone().Execute();} Dapper 鉴于Dapper是显式的所以是由开发人员确保只选取必需的列。 public IListEmployeeOfficePhone GetOfficePhoneNumbers(){using (var con new SqlConnection(m_ConnectionString)){con.Open();return con.QueryEmployeeOfficePhone( SELECT e.EmployeeKey, e.FirstName, e.LastName, e.OfficePhone FROM HR.Employee e).AsList();}} 实体框架 实体框架需要额外的操作步骤这些步骤常因为有些繁琐而被忽视。 通过在调用ToList前就包括了额外的选择语句实体架构可生成正确的SQL语句并避免从数据库返回过多的信息。 public IListEmployeeOfficePhone GetOfficePhoneNumbers(){using (var context new CodeFirstModels()){return context.Employees.Select(e new EmployeeOfficePhone(){EmployeeKey e.EmployeeKey,FirstName e.FirstName,LastName e.LastName,OfficePhone e.OfficePhone}).ToList();}} 使用投影做更新操作 固然在存在投影对象时直接从投影对象更新数据库是一种好的方法。该方法在Chain和Dapper的基本模式中是天然存在的。而在实体框架中则必须要在手工拷贝属性和编写Dapper风格的内嵌SQL这两种方法间做出选择。 Chain 注意任何未在投影类上具有匹配属性的列将不受到影响。 public void Update(EmployeeOfficePhone employee){return m_DataSource.Update(HR.Employee, employee).Execute();} Dapper public void Update(EmployeeOfficePhone employee){const string sql UPDATE HR.Employee
SET FirstName FirstName,LastName LastName,OfficePhone OfficePhone
WHERE EmployeeKey EmployeeKey
;using (var con new SqlConnection(m_ConnectionString)){con.Open();con.Execute(sql, employee);}} 实体框架 public void Update(EmployeeOfficePhone employee){using (var context new CodeFirstModels()){var entity context.Employees. Where(e e.EmployeeKey employee.EmployeeKey).First();entity.FirstName employee.FirstName;entity.LastName employee.LastName;entity.OfficePhone employee.OfficePhone;context.SaveChanges();}} 反射插入Reflexive Insert 现在我们来看一些更有意思的用例。反射插入意味着返回被插入的对象。做反射插入通常是为了获得默认的和计算的域。 模型 注意实体框架和Chain需要对属性进行注释这样库才会知道该域将由数据库予以设置。 [DatabaseGenerated(DatabaseGeneratedOption.Computed)] //Needed by EF[IgnoreOnInsert, IgnoreOnUpdate] //Needed by Chainpublic DateTime? CreatedDate { get; set; } Chain Chain允许将ToObject附加到任何插入或更新操作上。 public Employee InsertAndReturn(Employee employee){return m_DataSource.Insert(HR.Employee, employee) .ToObjectEmployee().Execute();} Dapper 使用Dapper的反射插入可以使用特定于数据库的功能实现例如OUTPUT语句。 public Employee InsertAndReturn(Employee employee){const string sql INSERT INTO HR.Employee(FirstName,MiddleName,LastName,Title,ManagerKey,OfficePhone,CellPhone)OUTPUT Inserted.EmployeeKey,Inserted.FirstName,Inserted.MiddleName,Inserted.LastName,Inserted.Title,Inserted.ManagerKey,Inserted.OfficePhone,Inserted.CellPhone,Inserted.CreatedDate
VALUES (FirstName,MiddleName,LastName,Title,ManagerKey,OfficePhone,CellPhone);;using (var con new SqlConnection(m_ConnectionString)){con.Open();return con.QueryEmployee(sql, employee).First();}} 如果一并考虑初学者级别模式更典型的做法是仅在Get方法之后调用Insert方法。 public Employee InsertAndReturn_Novice(Employee employee){return Get(Insert(employee));} 实体框架 使用前面提及的DatabaseGenerated属性你可以插入一个新的实体并读回它的计算的和/或默认的列。 public Employee InsertAndReturn(Employee employee){using (var context new CodeFirstModels()){context.Employees.Add(employee);context.SaveChanges();return employee;}} 受限更新/局部更新 有时应用并没有打算对每个列做更新尤其是当模型是直接源自于UI并可能混合了可更新域和不可更新域时。 Chain 在Chain中使用IgnoreOnInsert和IgnoreOnUpdate属性去限制插入和更新操作。为允许用数据库作为默认取值典型的做法是将这两个属性都置于CreatedDate类型的列中。为避免更新操作过程中的意外改变通常将IgnoreOnUpdate属性置于CreatedBy之类的列上。 Dapper 就显式编写的插入和更新语句而言Dapper最具灵活性。 实体框架 除了计算列列值为表达式实体框架并未给出一种简单的方法可声明某一列不参与插入或删除操作但可使用更新操作的“读-拷贝-写”read-copy-write模式模拟该行为。 更新或插入Upsert操作 经常需要作为一个单一操作完成记录的插入或者更新尤其是在使用自然主键natural key时。 Chain 在Chain中Upsert操作的实现使用了与插入和删除相同的设计。所生成的SQL随数据库引擎不同而各异例如SQL Server使用了MERGESQLit使用了一系列语句。 public int Upsert(Employee employee){return m_DataSource.Upsert(HR.Employee, employee). ToInt32().Execute();} Dapper 在Dapper中Upsert操作的实现需要多轮的来回交互或是需要比较复杂的特定于数据库的SQL语句。本文对此不作阐述。 实体框架 在实体框架中这过程函数都可以用“这”指代仅作为被改进的更新操作的一个变体。 public int Upsert(Employee employee){using (var context new CodeFirstModels()){if(employee.EmployeeKey 0)context.Entry(employee).State EntityState.Added;elsecontext.Entry(employee).State EntityState.Modified;context.SaveChanges();return employee.EmployeeKey;}} 性能 虽然本文所采用的主要基准测试是代码量和易用性但是对实际性能的考虑也是非常有用的。 所有的性能基准测试中都包括了预热过程其后是对主循环做1000次迭代操作。每次测试中都使用了同样的模型模型使用实体框架的代码优先Code First技术从数据库代码生成器产生。所有迭代都相当于共计13个基本CRUD操作其中包括创建、读取、更新和删除操作。 我要澄清的是这里所做的仅是一些粗略的测试使用了任何人在刚开始接触这些库时通常就会看到的代码类型。当然一些高级技术可以改进每个测试的性能有时甚至是极大地改进。 BenchmarkDotNet计时 Chain平均3.4160毫秒标准偏差为0.2764毫秒未使用经编译的物化器Compiled Materializers的Chain平均3.0955毫秒标准偏差0.1391毫秒Dapper平均2.7250毫秒标准偏差0.1840毫秒实体框架初学者平均13.1078毫秒标准偏差0.4649毫秒实体框架中级用户平均10.11498毫秒标准偏差0.1952毫秒实体框架未使用AsNoTracking的中级用户平均9.7290毫秒标准偏差0.3281毫秒。 结论 虽然可使用任何ORM框架去实现基本的仓储模式但是各种实现的性能和所需的代码量具有显著的差异。选取实现方式时需要对这些因素进行平衡此外还需考虑数据库可移植性、跨平台支持和开发人员经验等。 在该系列文章的第二部分我们将着眼于那些不仅将仓储模式作为瘦抽象层的高级用例。 你可以在GitHub上获取本文的代码。 关于作者 Jonathan Allen的首份工作是在上世纪九十年代末做诊所的MIS项目Allen将项目逐步由Access和Excel升级到企业级的解决方法。在从事为财政部门编写自动交易系统代码的工作五年之后他成为项目顾问参与了包括机器人仓库UI、癌症研究软件中间层、主要房地产保险企业的大数据需求等在内的各种行业项目。在闲暇时间他喜欢研究源于16世纪的武术并为其撰写文章。 原文地址http://www.infoq.com/cn/articles/repository-implementation-strategies.NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注