本文转载自微信公众号《DotNET技术圈》,作者MichaelHoagland。转载本文请联系DotNET技术圈公众号。前言这是一个对话式讨论,讨论了一个严重的问题趋势,即我看到实体框架在从初级团队到架构师团队的各种规模的组织中的高利用率。这不是一个如何做的问题,也不适合新手。如果您对某些内容感兴趣,或者对我提到的内容感到好奇,那么Google就是您的朋友。这也是我的第一篇博文。欢迎批评。历史和功能介绍(按版本)首先,让我们简要回顾一下EF随着时间的推移推出的功能。这绝不是详尽无遗的,当然也不会通过更新到主要版本来列出所有内容。它只是提醒了到目前为止EF的故事。EF/EF3.5·DBFirstEF4.0·延迟加载·迁移·POCOsEF5·枚举·SpatialEF6·异步·拦截·日志记录·NuGet安装·RecoveryEF7/Core·CodeFirstOnly·内存支持·有限批处理·非关系支持看到这个零星的发布和微软在开发界的普遍声誉,EntityFramework是一个糟糕的惊喜,而不是因为它是我即将解决的问题的借口。功能似乎会根据您使用的次要版本随机出现。所以即使安装了同一个大版本,你习惯了一些东西,去另一个环境,声明一下,在他们现有的框架上试试,即使不行,你还是会“告诉你”看起来服务于进一步深化已经确立的地位。基本上,一个典型的EF故事是这样的:高级人员:“让我们使用EF和存储库模式吧!”其他开发人员:“Idk,还没有听到好消息。”前辈:“不,那太好了!看到这个例子了吗?”开发人员:“嗯,好的。“起初,它在可接受的范围内工作。然而,随着它的发展,开始出现呆滞,人们开始抱怨。要么是因为我们行业偿还技术债务的糟糕状况,要么是因为完全拒绝看仓库模型首先是技术债务,所以整个部门的聪明人都坐在旁边,只是得出结论,“EF是垃圾”,而不是他们的使用是垃圾。我在这里试图解决的是后者,并向您展示如何避免这种陷阱。EF在我职业生涯的早期抽象了Sql过程,我开始直接通过ADO使用经典的ASP和SQLServer。我在一个非常小的网络部门工作,所以我经常不得不自己进入数据库创建表和执行任务。在一系列复制/粘贴部署、生产测试等过程中,我很快学会了SQLServer的所有技巧。”这个如何?不,该产品页面仍然无法加载。那个怎么样?!不,仍然无法加载。来吧……这个?成功!通过几乎在黑暗中跌跌撞撞,我什至在高中毕业之前就对索引、视图、复制、安全权限等有了很多了解。使用结构化环境进入我的前几个地方,然后使用EntityFramework让我脸红。它根本没有我习惯的任何选项。所以,我跳上了船,但没过多久我就开始抱怨了。如果你有金鱼记忆,让我重申一下,我习惯于随意调整所有杠杆。当我遇到问题时,我会调查。通常,我发现诸如索引利用率之类的关键组件被完全忽略了。当我提出这些问题时,有人告诉我EF的错在于不知道如何利用它们,我们来这里是为了处理业务问题,而不是让Microsoft为他们解决问题。老实说,我基本上还是初级开发人员。我为什么不同意?存储库模式的问题单击以访问存储库模式。仓库模型的问题是双重的。存储库模式的问题是双重的。首先,它要求您预先声明绑定的应用程序如何与数据库交互。即使您构建了这些允许您传入表达式、字典或异常动态的超级复杂方法,并且您发挥了创造力,您所做的一切也只是制造了一场维护噩梦。“但来电者可以定义他们想要什么!”不,他们不能。当然,它们可以指向实体并通常定义要选择的数据的形状,但它们不能确定诸如字段选择之类的事情。他们没有办法说他们需要以友好的方式预加载数据或延迟数据。他们不能在一个实例中说他们也需要来自这里或那里的数据,但在下一个实例中他们只需要找到目标实体,除非神圣的存储库允许他们这样做。相反,您会得到这些将您的应用程序链接在一起的全有或全无的决定,我们想知道为什么它会如此迅速地降级。我真的希望你的水晶球比我的好。其次,即使是Microsoft自己的示例也没有使用某些实习生可能编写的正确接口。所以,我说,每个人都做错了。EF的常识是使用repositorypattern,而由于repositorypattern的文档和例子本身是不正确的,所以没有人会让EF做它设计的事情,因为知识的来源是有毒的。面对这种情况,听到很多人抱怨MVC教程的例子直接使用DbContext,抱怨不够稳定,不是几乎没有人稳定,那是另一篇博文。(大多数软件直接跳转到ID,忽略其余部分。)让数据库只做数据库的事情因为SQLServer是EF中最常用的后备数据存储,所以它不是一个干净的软件。TooMessy,其中包含大量场景。如果您希望您的应用程序实际使用您支付的巨额许可费用的一小部分,请停止将SQL限制为比SELECT*更好的EF驱动的地狱荒地。然后,我们喜欢抱怨事情进展缓慢。如果您不让EF在适当的情况下利用功能,您可能不会意识到该平台的潜力。必须浪费数十亿美元的许可和开发成本,使用只会降低SQLServer单元利用率的独特功能,即使应用程序以截然不同的方式增长也是如此。这是一种直觉,但看到我在大大小小的公司中看到的愚蠢天真的存储库实现,很难看出我哪里完全错了。这对我们自己、我们的雇主和彼此都不利。实体框架仍然逐步锁定底层数据存储的工作方式。在SQLServer中,这意味着连接性能、视图和索引利用率、存储过程调用等。这就像将乳胶手套称为手的抽象。它不是,而且EF不是它所依赖的存储机制的抽象。相反,它是一组通用的API,使我们能够以统一的方式访问数据。由于我刚才陈述的原因,这不是抽象(我们不能以任何方式否认或减轻底层实现的行为)。因此,我们必须在我们的代码中考虑那些显式或隐式破坏抽象的行为。如果我们想假装它是抽象的,我们唯一能做的就是把头埋在沙子里,当事情变得笨拙时继续呻吟。最近,我提供了一个架构师的建议:让数据库定义视图并将EF指向视图而不是表,你知道的,它允许dba真正完成他们的工作并使数据库能够在不破坏应用程序的情况下完成它代码进行更改。这并不困难,但问题无处不在,所以大多数人在他们过于熟悉的环境中看不到过去。那么,我们该怎么做呢?使用IQueryable而不是IEnumerable正确使用EntityFramework的第一步是打破对IEnumerable的依赖。在谈论断开连接的商店时,这很糟糕。IEnumerable唯一提供的是延迟执行。如果这是您希望从ORM中获得的唯一功能,那么您就不需要ORM。IEnumerable隐藏数据存储使用的原因是它们在表示中是一劳永逸的。即使随着应用程序的增长,即使新方法被添加到存储库中,返回IEnumerable的旧实现对它们所处的新世界也是盲目、聋哑和愚蠢的。您实际上是在强制代码与数据布局和期望一起工作。就像几年前首次实施时一样。这是开发人员的错,但EF是罪魁祸首。但是,IQueryable可以变形并更改为它的给定上下文。它可以评估实例,即使子句被传递和添加,例如个人调用的需要。如果DbContext已经获取了数据,它仍然可以在非常快速地重复调用之前从缓存中检索实体,从而使热路径保持凉爽。更重要的是,它还提供了一些功能,例如如果底层提供者支持的话,让我们流式传输数据;在不实例化List对象的情况下加载数据以使其对堆更友好;检查底层类型,以便我们可以加载数据在复杂的工作流中做出明智的决定;访问底层上下文等。这些都是允许您的代码在不打破抽象障碍的情况下真正理解正在发生的事情的所有功能,因为EF不是抽象。顺便说一句,抽象应该是使用EF的组件,而不是EF本身。我在很多次和程序员的讨论中表达了自己的观点,表达了一些需求,但是很多解决方案都被我们打着“抽象”的旗号咆哮着,我们乐此不疲地把自己拧成一个圈套,这样我们才能继续作为实体存在。习惯匿名类型我听说过的关于EF的最大抱怨可能是它检索了多少该死的数据。谁定义实体?英孚?不!您做到了从本质上讲,您不必责备太多,因为每个表的实体似乎对所有人都是可见的。不过,不管实体有多大,我们都不需要受到阻碍。将匿名类型传递给EF查询将导致EF仅选择您定义的字段。一个“不可重构”的几十列的怪物表可以分解成你实际需要的3或4个字段。一次选择整个实体并假装无能为力的痴迷只能被描述为一种集体歇斯底里的形式,我们在其中哭泣,“我看不到你!”为工作使用正确的工具你知道所有MicrosoftPress的封面上都有工具吗?你知道,除了有些人只是随机选择图像之外,还有其他原因。大多数工具不仅仅是螺丝刀或刨床。有一些非常奇怪的应用程序没有明显的应用程序,但可以肯定的是,它们有一个目的并且擅长于此。“正确的工具”这句口头禅经常被重复,但我们并没有真正停下来思考工作,更不用说工具了。以下是EF的一些功能。自.Net2.0以来与EF数据量密切相关的SqlBulkCopy的另一个大抱怨是EF检索它的方式,据称它无法处理大量数据。我喜欢开发人员的二元性。我想让您知道,结合下面讨论的AsStreaming、ReactiveExtensions和SqlBulkCopy,我可以在一分钟内检索、转换和推送数百万条记录,而无需创建完全基于代码的ETL解决方案从小记录到中等大小的记录(比如5-100亿条记录)并且仍然具有良好的性能。如果您需要更多,可以使用更专业的工具。但是,不要说EntityFramework不能处理大量数据。您的代码无法处理大量数据。英孚没问题。可悲的是,自2005年以来我们就有了SqlBulkCopy,但我们假装我们的工具箱中有这个大洞。问题解决了。重新发明轮子的理由为零。你猜怎么了?它还支持流媒体!AsTrackingvsAsNoTracking我觉得我的成绩很差。对EF的另一个大抱怨是它的数据缓存。您几乎总是可以告诉DbContext摆脱缓存的实体。不过最近,我们获得了将此设置为EntityFrameworkCore中的默认策略的能力。相反,我们可以选择要跟踪的内容,而不是我们不想跟踪的内容。我很乐意承认的一个烦恼是您仍然需要分离实体。EntityFramework中的流式查询通常在返回结果之前缓冲所有结果。流式处理解决了这个问题,并让您在数据进入您的应用程序时立即开始处理数据。您既可以更快地开始工作,又可以使服务器对内存更友好。特殊的雪花在开发人员中,我看到了一种令人不安的趋势。缺乏探索和发明的欲望。我们想要在不知道细节的情况下“工作”的开箱即用的解决方案。尽管代码不是魔法,但我们仍然相信看不见的魔法。我采用的一般方法不是构建这些固定存储库,而是构建扩展,使我们的应用程序以我们需要的独特方式运行。想要在长时间运行的过程中缓存数据的好处,但又不想在给定操作之后持久化?对我来说,这听起来像是DbContext的完美扩展方法,它获取一些实体,为了缓存的好处对其进行处理,然后在返回之前清除缓存。另一种扩展方法是在操作完成后分离所有这些实体。不要害怕我在这里谈论DbContext因为很多人都在对待它。它被视为一件大而笨重的东西,如果你不小心,它可能会把你的孩子偷走。我们花了很长时间才让只有少数组件知道DbContext的存在。这将我们的实现进一步扼杀在存储库中。由于我们必须为任何类型的数据遍历存储库,因此当发生更改时,我们需要周期性地违反“开放/关闭”原则,或者被迫接受存储库指示的决策膨胀的权衡,并且在使用时要格外小心给它。释放DbContext。如果模块需要数据,请不要自欺欺人地说DbContext还不是依赖项。我可以向您保证,如果您对它的可访问性感到满意,它就会消除“如果人们弄错了怎么办?!”的问题。神秘主义。它实际上会让我们整体变得更好。如果有人可以提交顽皮的代码并且至少有一次未经测试就投入生产,那么您就没有真正的发布控制或QA。像隐藏DbContext这样的策略是对组织中已经流血的伤口的权宜之计,并不能真正缓解真正的问题。停止找借口,我们程序员必须停止表现得像解决我们遇到的问题或必须使用正确的节点组合。被他们当作替罪羊EntityFramework是一个很好的工具,可以用于某些事情。我们拥有十年的工具已经足以满足我们的大部分需求。一个又一个错误的决定最终成为一个让我们陷入困境的错误决定。调整你的工具以适应。尝试新事物。可以肯定的是,我们只能怪自己。
