1.引言
Entity Framework(EF)是微软公司发布的一款ORM(Object Relational Mapping)框架,它的出现彻底改变了开发者操作数据库的方式,让开发人员把精力集中在业务代码开发上,
而不是以往ORM需要我们去关注对象模型与关系模型映射,从而提升了开发效率。EF不仅仅局限于SQL Server数据库,它也可以运行在多种数据库之上,如MySQL、Oracle等等。但是,在本文中我将重点介绍EF对于SQL Server的实现,探讨如何提升应用性能。
2.EF对SQL Server的优势
在习惯传统ADO.NET编程的时候,我们通常需要手动将SQL语句拼接成字符串交给SqlDataAdapter,再由SqlDataAdapter执行查询。这种方式存在以下问题:
2.1 程序的可维护性较差
由于每一条SQL语句都是由字符串加入参数构成的,查错极其困难。同时,由于没有模型约束的情况下操作数据库,可能导致命名混乱甚至溢出等现象。
2.2 代码的可读性不够强
手动拼接SQL语句导致代码非常冗长,而且使用类似C#这种高级编程语言去书写低阶语言SQL,在代码可读性上也有一定影响。
相对于传统的操作数据库方式,Entity Framework可以很好地解决这些问题:
2.3 EF提供对象关系映射,方便维护
使用EF时,我们只需要定义好数据库表,以及每个表对应的实体对象,而无需编写SQL语句,EF框架将会自动将实体转换为SQL语句并执行。
下面是此过程的示例:
//Person 类对应 person 表
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
//查询
using(var dbContext = new MyDbContext())
{
var persons = dbContext.Persons.Where(o => o.Name == "张三" && o.Age > 21);
}
上述代码中,我们只需要使用LINQ对象来查询数据,EF则会自动生成SQL语句来查询数据库中的数据。
2.4 自动排除不必要的查询和数据转换
通过使用EF,我们可以自动排除不必要的查询和数据转换,从而减少数据库的负担和在数据层面上降低延迟时间。而且,得益于EF的优势,我们也可以轻松地自定义仓储逻辑和其他需要处理的逻辑。
3.针对SQL Server的性能提升
EF默认情况下,对于SQL Server的支持已经很完整。这些支持包括(但不限于):
合适的事务的设计方式
escaped parameters
SQL Server specific types
SQL Server performance counters.
但是,仅仅依赖这些EF自带的功能,可能会由于某些原因导致应用程序性能无法得到充分发挥。因此,从以下几个方面针对SQL Server对EF进行性能提升。
3.1 多数据库查询
在一个请求过程中,可能出现对多个数据库表进行查询的情况,而此时在单个数据库的限制下,查询时间可能会很长,导致整个请求的执行效率降低。
这个时候,就需要从架构设计的角度来考虑。通过设计多个SQL Server数据库实例来减轻单个数据库的负担和降低延迟时间。
下述代码是来自于Stack Overflow上User David Watkinson的建议,可以实现在EF中使用多个数据库实例进行查询的操作:
public class MyDbContext: DbContext
{
private readonly string _connectionString;
public MyDbContext(string connectionString) : base(connectionString)
{
_connectionString = connectionString;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Create config...
var provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
var connection = provider.CreateConnection();
connection.ConnectionString = _connectionString;
modelBuilder.Entity()
.MapToStoredProcedures(new StoredProcedureMapping
{
TypeName = "Person",
Parameters =
{
new SqlParameter
{
Name = "Id",
ParameterName = "@id",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.InputOutput
},
new SqlParameter
{
Name = "Name",
ParameterName = "@name",
SqlDbType = SqlDbType.NVarChar,
Size = 100,
Direction = ParameterDirection.Input
},
new SqlParameter
{
Name = "Age",
ParameterName = "@age",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Input
}
},
InsertFunctionName = "Insert_Person",
UpdateFunctionName = "Update_Person",
DeleteFunctionName = "Delete_Person",
ResultColumnName = "Id"
}).MapToStoredProcedures(new StoredProcedureMapping
{
TypeName = "Person",
Parameters =
{
new SqlParameter
{
Name = "Name",
ParameterName = "@name",
SqlDbType = SqlDbType.NVarChar,
Size = 100,
Direction = ParameterDirection.Input
}
},
FunctionName = "Person_Search",
ResultColumnName = "Id"
});
modelBuilder.Entity()
.MapToStoredProcedures(new StoredProcedureMapping
{
TypeName = "ProductOrder",
Parameters =
{
new SqlParameter
{
Name = "Id",
ParameterName = "@id",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.InputOutput
},
new SqlParameter
{
Name = "PersonId",
ParameterName = "@personId",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Input
},
new SqlParameter
{
Name = "ProductId",
ParameterName = "@productId",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Input
}
},
InsertFunctionName = "Insert_ProductOrder",
UpdateFunctionName = "Update_ProductOrder",
DeleteFunctionName = "Delete_ProductOrder",
ResultColumnName = "Id"
});
}
}
使用EF和多个数据库实例可以更好地提升查询性能,这是一种在数据库类架构设计和代码编写过程中都比较重要的优化手段。
3.2 使用存储过程
通常情况下,在开发中我们建议使用存储过程来提升查询性能。
存储过程可以通过一次编译就可重复使用,从而在确保数据安全性的同时,减小了重复编译的时间和开发人员编写代码的工作量,更重要的是提高了性能。
下面是使用存储过程的示例代码:
//创建存储过程
CREATE PROCEDURE GetProducts
@param1 NVARCHAR(100)
AS
BEGIN
SELECT *
FROM Products
WHERE ProductName LIKE @param1 + '%';
END;
//在EF中调用存储过程
using (var context = BloggingContext())
{
var blog = context.Blogs.SqlQuery("EXEC GetProducts @p0", searchTerm).FirstOrDefault();
}
增加对存储过程的使用可以极大地改善请求时的性能,并且可以更好地维护应用程序。但在使用存储过程时需要注意的是,因为存储过程对于EF框架是不透明的,所以需要更加谨慎而周到地处理。将它们封装在Repository中,确保存储过程返回的数据被映射到实体对象中。
3.3 批处理
批处理可以使得相邻的查询尽可能地集中在一起,从而减少数据库连接次数。在使用EF框架时,可以使用批处理命令来实现。
using (var context = new MyDbContext())
{
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
foreach (var product in products)
{
context.Products.Add(product);
}
context.SaveChanges();
}
上述代码中,context.Configuration.AutoDetectChangesEnabled的设置可以禁用EF对每个实体的状态自动检测。在禁用状态下,当应用程序调用SaveChanges()方法时,EF会将所有状态为Added、Modified、或Deleted的所有实体的相关SQL语句一次性提交。
3.4 使用无跟踪实体
默认情况下,EF会跟踪查询到的实体状态并在Dispose对象时将修改保存到数据库。这对于较小的查询给开发人员带来了很大的便捷,但是对于较大的查询,将全部跟踪的实体传到上下文并不是一个理想的解决方案,因为它会增加上下文跟踪状态的大小,降低了性能,同时也增加了在多个实例中执行同一个查询的并发性问题。
此时我们建议使用无跟踪实体,不需要对象上下文跟踪状态。而且,使用无跟踪实体后,在应用程序中也可以进行许多复杂操作,如批处理和大查询操作。
下面是使用无跟踪实体的示例代码:
using (var context = new MyDbContext())
{
var persons = context.Persons.AsNoTracking().Where(o => o.Age > 21 && o.Id > 0).ToList();
}
我们需要在查询或查询时通知EF不需要跟踪实体状态,要放在AsNoTracking()扩展方法(也可以使用Find()或SingleOrDefault()方法)中,否则EF仍然会跟踪实体。
4.结语
作为众多ORM框架中的一个,Entity Framework为我们带来了诸多便利和效率。然而,不同的数据库对ORM的实现总是存在一定的差异,这时就需要在实践中根据特定的情况对EF进行一定的调整和优化,以达到更好的性能提升。如本文所述,我们应该注意多数据库查询、使用存储过程、批量处理以及使用无跟踪实体等等。通过这些方法,我们可以让EF的性能得到最大的发挥和提升。