首页 后端开发 C++ 使用存储库甚至 ORM 进行代码中的数据访问

使用存储库甚至 ORM 进行代码中的数据访问

Dec 25, 2024 am 11:21 AM

Data access in code, using repositories, even with ORMs

简介

在 .NET 世界中,访问数据库最常用的方法之一是使用实体框架 (EF),这是一种与语言语法紧密集成的对象关系映射器 (ORM)。使用 .NET 语言原生的语言集成查询 (LINQ),使数据访问感觉就像使用普通的 .NET 集合一样,而无需了解太多 SQL 知识。这有其优点和缺点,我将尽量不在这里咆哮。但它始终造成的问题之一是软件项目结构、抽象级别和最终单元测试的混乱。

这篇文章将尝试解释为什么存储库抽象总是有用的。请注意,许多人使用存储库作为抽象数据访问的术语,虽然也有与类似事物相关的存储库软件模式,但它不是同一件事。在这里,我将把存储库称为一系列抽象数据访问的实现细节的接口,并完全忽略设计模式。

历史

如果您意识到这一点,请随意跳过这一点,但我必须首先解决我们如何开始想到存储库的想法。

在史前时期,代码只是按原样编写,没有结构,一切都在做你想让它做或至少希望它做的事情。没有自动化测试,只有手动黑客和测试,直到它起作用。每个应用程序都是用现有的任何东西编写的,对硬件要求的关注比代码结构、重用或可读性更重要。这就是恐龙被杀死的原因!真实的事实。

慢慢地,模式开始出现。特别是对于业务应用程序,业务代码、数据持久性和用户界面之间存在明显的分离。这些被称为层,很快被分成不同的项目,不仅因为它们涵盖了不同的关注点,而且因为构建它们所需的技能特别不同。 UI 设计与代码逻辑工作非常不同,与 SQL 或任何用于持久数据的语言或系统也非常不同。

因此,业务和数据层的交互是通过抽象成接口和模型来完成的。作为商务舱,您不会要求表中的条目列表,您将需要复杂对象的过滤列表。数据层有责任访问持久保存的内容并将其映射到业务可以理解的内容。这些抽象开始被称为存储库。

在数据访问的较低层,CRUD 等模式很快就占据了主导地位:您定义了表等结构化持久性容器,并且可以创建、读取、更新或删除记录。在代码中,这种逻辑将被抽象为集合,例如列表、字典或数组。因此,目前还有一种观点认为存储库的行为应该像集合一样,甚至可能足够通用,除了实际的创建、读取、更新和删除之外没有其他方法。

但是,我强烈不同意。作为业务数据访问的抽象,它们应该尽可能远离数据访问模式,而不是根据业务需求进行建模。这是实体框架(尤其是许多其他 ORM)的思维方式开始与存储库的原始想法发生冲突的地方,最终呼吁永远不要将存储库与 EF 一起使用,称其为反模式。

更多层数

模型之间的父子关系会产生很多混乱。就像一个部门实体,里面有人员。部门存储库是否应该返回包含人员的模型?也许不是。那么,我们如何将存储库分成部门(没有人员)和人员,然后有一个单独的抽象来映射到业务模型?

当我们将业务层分成子层时,混乱实际上会增加。例如,大多数人所说的业务服务是仅将特定业务逻辑应用于特定类型的业务模型的抽象。假设您的应用程序与人一起工作,因此您有一个名为 Person 的模型。处理人员的类将是 PeopleService,它将通过 PeopleRepository 从持久层获取业务模型,但也可以执行其他操作,包括数据模型和业务模型之间的映射或仅与人员相关的特定工作,例如计算工资。然而,大多数业务逻辑使用多种类型的模型,因此服务最终成为存储库上的映射包装器,几乎没有额外的责任。

现在假设您正在使用 EF 访问数据。您必须声明一个 DbContext 类,其中包含映射到 SQL 表的实体集合。您可以使用 LINQ 来迭代、过滤和映射它们,这些数据会在后台有效地转换为 SQL 命令,并为您提供所需的内容,以及分层的父子结构。该转换还负责内部业务数据类型的映射,例如特定的枚举或奇怪的数据结构。那么为什么你甚至需要存储库,甚至可能需要服务?

我相信,虽然更多的抽象层似乎是毫无意义的开销,但它们增加了人们对项目的理解,并提高了变革的速度和质量。显然,这是一种平衡,我见过一些系统架构明显要求所有软件设计模式都在任何地方使用。抽象只有在提高代码可读性和关​​注点分离时才有用。

原因

EF 变得麻烦的环境之一是单元测试。 DbContext 是一个复杂的系统,具有大量依赖项,必须花费很大的精力手动模拟。因此,微软提出了一个想法:内存数据库提供程序。因此,为了测试任何内容,您只需使用内存数据库即可完成。

请注意,在 Microsoft 页面上,此测试方法现在标记为“不推荐”。另请注意,即使在这些示例中,EF 也是由存储库抽象的。

虽然内存数据库测试有效,但它们添加了几个不容易解决的问题:

  • 设置内存中的 DbContext 需要现有实体的所有依赖项
  • 为每个测试设置和启动内存数据库都很慢
  • 为了获得有效的数据库输出,您需要设置的内容远多于您想要原子测试的内容

因此,最终发生的情况是,人们在“帮助程序”方法中设置数据库中的所有内容,然后创建从这种难以理解且复杂的方法开始的测试,以测试甚至最小的功能。如果没有此设置,任何包含 EF 代码的代码都将无法测试。

因此使用存储库的原因之一是将测试抽象移至 DbContext 之上。现在您根本不需要数据库,只需要一个存储库模拟。然后使用真实数据库在集成测试中测试您的存储库本身。内存数据库非常接近真实数据库,但也略有不同。

另一个原因(我承认我在现实生活中很少看到它具有实际价值)是您可能想要改变访问数据的方式。也许你想换成NoSql,或者一些内存分布式缓存系统。或者,更有可能的是,您从一个数据库结构开始,也许是一个整体数据库,现在您想将其重构为具有不同表结构的多个数据库。让我立即告诉您,如果没有存储库,这是不可能的。

特定于实体框架,您获得的实体是映射到数据库的活动记录。您对一个实体进行更改并保存对另一个实体的更改,然后您突然也会在数据库中更新第一个实体。或者也许您没有,因为您没有包含某些内容,或者上下文已更改。

EF 的支持者总是将实体跟踪宣传为一件非常积极的事情。假设您从数据库中获取一个实体,然后执行一些业务,然后更新该实体并保存它。通过存储库,您将获取数据,然后开展业务,然后再次获取数据以执行一些更新。 EF 会将其保留在内存中,知道它在更改之前没有更新,因此它永远不会读取它两次。这是真的。他们描述了数据库的内存缓存,它以某种方式感知数据库更改并跟踪您从数据库处理的所有内容,除非另有指示,否则将数据库条目双向映射到复杂的 C# 实体并来回跟踪更改,同时深度嵌入在业务代码中。就我个人而言,我认为这种过多的责任和缺乏关注点分离比使用它所获得的任何性能更具破坏性。此外,通过一些初步的努力,所有这些功能仍然可以在存储库中抽象,甚至可以在存储库的另一层内存缓存中抽象,同时保持业务、缓存和数据访问之间的清晰边界。

事实上,所有这一切的实际困难在于确定应该有单独关注点的系统之间的边界。例如,通过将过滤逻辑移至数据库中的存储过程,可以获得大量性能,但这会失去所用算法的可测试性和可读性。相反,使用 EF 或其他一些机制将所有逻辑转移到代码中,性能较差,有时甚至不可行。或者数据实体成为业务实体的点在哪里(参见上面的部门和人员示例)?

也许最好的策略是首先定义这些边界,然后决定采用哪些技术和设计。

我的结论

我相信应该始终使用服务和存储库抽象,即使存储库在底层使用实体框架或其他 ORM。这一切都归结为关注点分离。我永远不会认为实体框架是一个有用的软件抽象,因为它带有如此多的包袱,因此存储库非常适合在代码中对其进行抽象。 EF 是一个有用的抽象,但用于数据库访问,而不是在软件中。

我的软件编写理念是,从应用程序需求开始,为这些需求创建组件,并使用接口抽象任何较低级别的功能。然后,您在下一个级别重复该过程,始终确保代码可读,并且不需要了解使用的组件或当前级别使用的组件。如果情况并非如此,那么您就已经很好地分离了关注点。因此,由于没有任何业务应用程序需要使用特定的数据库或 ORM,因此数据层抽象应该隐藏所有这些知识。

企业想要什么?过滤后的人员列表? var people = service.GetFilteredListOfPeople(filter);不多不少。并且服务方法只会 return mapPeople(repo.GetFilteredListOfPeople(mappedFilter));同样,不多不少。回购如何获取人员、拯救人员或执行其他任何操作不是服务关心的问题。您需要缓存,然后实现一些实现 IPeopleRepository 并依赖于 IPeopleRepository 的缓存机制。您想要映射,请实现正确的 IMapper 接口。等等。

我希望我在这篇文章中没有过于冗长。我特意保留了代码示例,因为这更多的是一个概念问题,而不是软件问题。实体框架可能是我在这里抱怨的大部分目标,但这适用于任何在小事情上神奇地帮助你,但却破坏了重要事情的系统。

希望有帮助!

以上是使用存储库甚至 ORM 进行代码中的数据访问的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

C语言数据结构:树和图的数据表示与操作 C语言数据结构:树和图的数据表示与操作 Apr 04, 2025 am 11:18 AM

C语言数据结构:树和图的数据表示与操作树是一个层次结构的数据结构由节点组成,每个节点包含一个数据元素和指向其子节点的指针二叉树是一种特殊类型的树,其中每个节点最多有两个子节点数据表示structTreeNode{intdata;structTreeNode*left;structTreeNode*right;};操作创建树遍历树(先序、中序、后序)搜索树插入节点删除节点图是一个集合的数据结构,其中的元素是顶点,它们通过边连接在一起边可以是带权或无权的数据表示邻

C语言文件操作难题的幕后真相 C语言文件操作难题的幕后真相 Apr 04, 2025 am 11:24 AM

文件操作难题的真相:文件打开失败:权限不足、路径错误、文件被占用。数据写入失败:缓冲区已满、文件不可写、磁盘空间不足。其他常见问题:文件遍历缓慢、文本文件编码不正确、二进制文件读取错误。

c语言函数的基本要求有哪些 c语言函数的基本要求有哪些 Apr 03, 2025 pm 10:06 PM

C语言函数是代码模块化和程序搭建的基础。它们由声明(函数头)和定义(函数体)组成。C语言默认使用值传递参数,但也可使用地址传递修改外部变量。函数可以有返回值或无返回值,返回值类型必须与声明一致。函数命名应清晰易懂,使用驼峰或下划线命名法。遵循单一职责原则,保持函数简洁性,以提高可维护性和可读性。

c语言函数名定义 c语言函数名定义 Apr 03, 2025 pm 10:03 PM

C语言函数名定义包括:返回值类型、函数名、参数列表和函数体。函数名应清晰、简洁、统一风格,避免与关键字冲突。函数名具有作用域,可在声明后使用。函数指针允许将函数作为参数传递或赋值。常见错误包括命名冲突、参数类型不匹配和未声明的函数。性能优化重点在函数设计和实现上,而清晰、易读的代码至关重要。

c语言函数的概念 c语言函数的概念 Apr 03, 2025 pm 10:09 PM

C语言函数是可重复利用的代码块,它接收输入,执行操作,返回结果,可将代码模块化提高可复用性,降低复杂度。函数内部机制包含参数传递、函数执行、返回值,整个过程涉及优化如函数内联。编写好的函数遵循单一职责原则、参数数量少、命名规范、错误处理。指针与函数结合能实现更强大的功能,如修改外部变量值。函数指针将函数作为参数传递或存储地址,用于实现动态调用函数。理解函数特性和技巧是编写高效、可维护、易理解的C语言程序的关键。

c上标3下标5怎么算 c上标3下标5算法教程 c上标3下标5怎么算 c上标3下标5算法教程 Apr 03, 2025 pm 10:33 PM

C35 的计算本质上是组合数学,代表从 5 个元素中选择 3 个的组合数,其计算公式为 C53 = 5! / (3! * 2!),可通过循环避免直接计算阶乘以提高效率和避免溢出。另外,理解组合的本质和掌握高效的计算方法对于解决概率统计、密码学、算法设计等领域的许多问题至关重要。

CS-第 3 周 CS-第 3 周 Apr 04, 2025 am 06:06 AM

算法是解决问题的指令集,其执行速度和内存占用各不相同。编程中,许多算法都基于数据搜索和排序。本文将介绍几种数据检索和排序算法。线性搜索假设有一个数组[20,500,10,5,100,1,50],需要查找数字50。线性搜索算法会逐个检查数组中的每个元素,直到找到目标值或遍历完整个数组。算法流程图如下:线性搜索的伪代码如下:检查每个元素:如果找到目标值:返回true返回falseC语言实现:#include#includeintmain(void){i

C#与C:历史,进化和未来前景 C#与C:历史,进化和未来前景 Apr 19, 2025 am 12:07 AM

C#和C 的历史与演变各有特色,未来前景也不同。1.C 由BjarneStroustrup在1983年发明,旨在将面向对象编程引入C语言,其演变历程包括多次标准化,如C 11引入auto关键字和lambda表达式,C 20引入概念和协程,未来将专注于性能和系统级编程。2.C#由微软在2000年发布,结合C 和Java的优点,其演变注重简洁性和生产力,如C#2.0引入泛型,C#5.0引入异步编程,未来将专注于开发者的生产力和云计算。

See all articles