EntityFramework:是或否?

您处理问题的方式可能会造成EntityFramework审查的好坏之间的差异。 像其他所有事物一样,EF也有其特质和缺陷……

在继续之前,我们需要回答一个重要的问题:什么是EntityFramework(EF)?

EF是一个ORM(对象关系映射)框架,创建时考虑了ADO.NET数据访问。 ORM允许创建虚拟对象数据库以充当桥梁并简化对数据库的操作,遵循面向对象语言的结构,就像我们将数据库对象模拟为OOL对象一样。

自EntityFramework首次发布以来已经过去了九年。 尽管基本原理是相同的,但它已经演化,允许更多功能,并且大多数情况下已经变得更加稳定(在.NET Framework 3.5之前,EF并不是使用的最可靠的ORM)。

但是,.NET程序员在EF的质量和缺陷之间仍然存在很大分歧。

我听到很多:

  • “ EntityFramework ?! 我拒绝用那个垃圾!”
  • “ EntityFramework ?! 这不能用于处理具有超过一千条记录的所有内容。 它阻塞了一切!”
  • “我以前使用过它,但是它又笨又慢,所以我不喜欢,也从未尝试过再次使用它!”

而且我也听到很多:

  • “我只使用EntityFramework; 这是我见过的为数据库访问创建的最好的东西!”
  • “我喜欢EF,我不使用或不需要任何其他操作即可对数据库进行操作/查询!”

我相信大多数.NET程序员都属于这两种极端的“爱恨交加”类别之一。

谁说的对?

好吧,如果您是“直接用于一切”的EF爱好者,很抱歉让您失望,但我认为您是不对的。

您“讨厌”的想法是“哈哈,我告诉过您,EF不好”,很抱歉也让您失望,但您也不对。

答案是最明显的,这个问题没有对与错,只有选择和不同的用途。 在某些情况下,EF很棒,而在其他情况下则不然。 与任何其他ORM(例如,休眠)没有太大区别。

答案是肯定的—如果您知道如何使用它以及何时使用它。 如果您不熟悉EF,并且不确定它在内部如何工作,那么最好在进行“ EF缓慢且只会给我带来麻烦”之前进行一些研究。

让我们开始吧。

假设我们有以下来自“很好的旧版本”的表(现在可能不是现在使用的)ASP.NET成员资格:

假设我们要获得所有一个月未登录的用户,然后,我们希望进一步过滤并加入另一个表,以按用户名和电子邮件(通过特定的邮件服务器“ @SomeEmailServer”过滤)获得这些用户。 com”。

看下面的例子:

  EFTest.EFTestEntities dataCtx =新的EFTestEntities(); List  usersNotLoggedInForAMonth = dataCtx.aspnet_Membership 
.Where(u => u.LastLoginDate.CompareTo(DateTime.Now.AddMonths(-1))<0).ToList(); var usersNLData =(来自用户中的成员NotLoggedInForAMonth
在memb.UserId上的dataCtx.aspnet_Users中加入用户等于user.UserId
其中memb.Email.TrimEnd()。EndsWith(“ @ SomeEmailServer.com”)
选择新的{user.UserName,memb.Email}
); foreach(usersNLData中的var用户)
{
SendEmail(user.UserName,user.Email,string.Format(“嘿{0},我们注意到您已经离开了一段时间!请回来!”,user.UserName)));
}

它出什么问题了?

 列出用户NotLoggedInForAMonth = dataCtx.aspnet_Membership 
.Where(u => u.LastLoginDate.CompareTo(DateTime.Now.AddMonths(-1))<0).ToList();

EF查询将转换为SQL查询,并在SQL Server端执行。 这使得获取数据非常有效,因为除了进行一些额外的工作(而且它不像许多人认为的那样占用大量资源)之外,它与您自己进行查询几乎相同(至少在大多数情况下)几乎是基本级别的SQLCommand。 很好,不是吗?

最常见的问题通常是在程序员方面:在中间查询时间触发SQL执行,这是因为它还不应该获取数据,并且查询结果是广泛的(太多的记录),而程序员甚至都没有考虑过。 在编程测试中,它可能不会引起注意(通常没有生产中的记录那么多),但是随着时间的流逝,记录的数量会随着时间的推移而变得越来越慢。

查看上面的示例,假设aspnet_Membership记录了1.000.000条记录,其中有一个月没有登录的用户,而其中只有100条记录了您要查找的电子邮件服务器(“ @ SomeEmailServer.com”)。 您将检索999.900条无用的记录,并且其中每条记录都具有无用的数据(因为您只需要两个字段(用户名和电子邮件)),使查询(和您的应用程序)的查询速度降低了数百倍,我什至没有提及对数据库服务器和网络的影响。 因此,在使用EF处理数据和查询的方式时,需要非常小心。

简单! 始终将数据查询保持为IQueryable,并确保在仅使用所需数据进行形式化查询之前,不要使用任何会触发数据查询执行的函数。

  IQueryable 用户NotLoggedInForAMonth = dataCtx.aspnet_Membership 
.Where(u => u.LastLoginDate.CompareTo(DateTime.Now.AddMonths(-1))<0);

使用ToList,ToArray甚至使用简单的foreach都要小心。 如果在正确的时间之前使用了所有这些(以及其他几个),将拨打灾难电话。

现在,我们已经检查了最常见的EF Linq-To-SQL错误之一,让我们检查另一个错误。

如前所述,EF允许进行简单,直接和快速的数据库查询。 问题是,当我们倾向于过度使用它并且对它不怎么考虑时。

让我们看一下上面的非常典型的例子。

我们的目的是从用户那里获取所有“附件”,并且我们希望阻止所有具有“管理员”角色的附件。

  EFTest.EFTestEntities dataCtx =新的EFTestEntities(); var users = dataCtx.aspnet_Users.Where(u => u.UserName.StartsWith(“ Anne”)); foreach(用户中的var用户) 
{
var userRoles =(来自dataCtx.vw_aspnet_UsersInRoles中的UserRoles
在UserRoles.RoleId上加入dataCtx.aspnet_Roles中的角色等于Roles.RoleId
其中UserRoles.UserId == user.UserId
选择Roles.RoleName
); 如果(userRoles.Contains(“ Admin”))
{
var userMemb = dataCtx.aspnet_Membership.Where(m => m.UserId == user.UserId).First();
userMemb.IsLockedOut = true;
dataCtx.SaveChanges();
}
}

这是怎么了

我们有两个EF查询和一个对数据库的SaveChanges(在这种情况下,将是更新)操作,它位于foreach中。 如果我们有1000个“ Annes”,则此循环将运行1000次。 就本地应用程序运行时中的处理而言,这通常是不相关的。 但是,当我们调用外部服务和数据库操作(总是有大量的等待时间和响应时间)时,一切都会有所不同。 每个循环中,我们对DB进行3次DB操作,结果是3000次操作。

如果对数据库的访问具有50ms的平均延迟,则这将意味着150.000ms(或2.5分钟),这对于这样一个简单的任务来说是极端的,我什至没有考虑SQL Server在执行和返回它上花费的时间。

那么,解决方案是什么?

查询应该在周期之外,并且对SaveChanges也是如此。 通常,某些任务的总操作数越少越好,并且最快,例如下面的示例(对数据库执行仅1个SaveChanges操作和仅1个查询):

  EFTest.EFTestEntities dataCtx =新的EFTestEntities(); var usersMemb =(来自dataCtx.aspnet_Users中的用户 
在Users.UserId上的dataCtx.aspnet_Membership中加入UserMemb等于UserMemb.UserId
在Users.UserId上的dataCtx.vw_aspnet_UsersInRoles中加入UserRoles等于UserRoles.UserId
在UserRoles.RoleId上加入dataCtx.aspnet_Roles中的角色等于Roles.RoleId
其中Users.UserName.StartsWith(“ Anne”)&& Roles.RoleName.Contains(“ Admin”)
选择UserMemb); foreach(usersMemb中的var用户)
{
user.IsLockedOut = true;
} dataCtx.SaveChanges();

同时,EF将此SaveChanges转换为几个更新查询,但仍然不是最佳选择。 对于大量记录,我建议创建一个存储过程并调用它(您也可以使用EF),如下所示:

 使用(EFTest.EFTestEntities dataCtxNC = new EFTestEntities()) 
{
dataCtx.BlockAdmins(“ Anne”);
}

还有其他提高EF效率的方法,例如禁用变更跟踪,如上面的示例(或仅应用于特定查询而非全部DBContext时,使用AsNoTracking()):

 使用(EFTest.EFTestEntities dataCtxNC = new EFTestEntities()) 
{
dataCtx.Configuration.AutoDetectChangesEnabled = false;
}

但这只是很大的行数的一种改进,而不是确定的解决方案。

EF应该被认为是一个很好的ORM框架,只要您谨慎并知道它的工作方式,以避免某些错误并造成性能问题,它就可以更快地进行开发,对DB的操作更加轻松快捷。

因此,EF可能非常有用,并可能在许多任务中为您提供帮助,但对于DB上的大型操作而言,EF可能不是最佳选择,您应该将其留给DB Server本身。

当您需要从代码中进行大量插入时,请确保在更广泛的操作和SqlBulkCopy(包含在名称空间System.Data.SqlClient中)上使用存储过程。 此外,还有一些基于SqlBulkCopy的EF扩展,即使使用EF,它们也可以帮助您提高性能。

由Cleverti开发人员Dinis Ferreira撰写


最初在 www.cleverti.com上 发布