面向对象的范例是应用软件的标准。 关系DBMS-在应用程序软件中存储数据的标准。 是的,您可以编写Haskell并将数据专门存储在ClickHouse中。 但这是关于主流的。
ORM允许您
在地球仪上捉弄
猫头鹰,以假装没有RDBMS,并且数据存储在更适合OOP的对象模型中。 仍然存在一个“小”的问题-这种抽象像许多其他问题一样,是“流程”。 在对象模型中的何处,有一个指向数据库外键和ID中另一个对象的链接。 在实体实现时,我们面临一个选择:
- 下载所有内容并从内存不足/超时中退出
- 明确指出我们要下载哪些依赖项,哪些不需要,并违反告诉不要问原则
- 使用惰性加载按需隐式加载依赖关系,并在调用的代码中获得性能问题
您应该剪掉哪种腿:左还是右?
如果TLDR惰性加载仅用于写入而在读取时不使用,则还不错。 但是,事情并不是那么简单,并且有很多细微差别。随着时间的流逝,我得出的结论是,在某些情况下,延迟加载和/或实体对ORM的依赖度较小。
在读取子系统中,始终仅读取DTO
在90%的情况下,正好在读取时出现了惰性加载问题。 我们得到实体列表,遍历并循环,然后开始选择所有必要的数据。 我们对数据库有很多查询。 在这种情况下,通常唯一需要做的就是获取数据,对其进行序列化并以JSON形式发送回去。 那么,为什么要加载实体呢? 无需将此数据添加到变更跟踪器UOW,即可读取整个实体以及“额外”字段。 相反,您始终可以编写
Select
或
ProjectTo
。 不需要延迟加载,因为来自
Select
的C#代码将转换为SQL并在数据库端执行。
如果我的逻辑未转换为SQL怎么办?
我建议不要进行“
客户评估” 。 首先,您可以
直接在sub中 “帮助”并添加对必要功能的支持。 在简单计算而非业务规则方面,这不是一个坏选择。 第二种选择:从实体中提取接口,并在实体和DTO中实现它。
例如,在数据库中有两个字段:“没有折扣的价格”和“有折扣的价格”。 如果填写了“折扣价格”字段,则使用它,如果没有填写,则使用通常价格的字段。 再添加一条规则。 当购买3种产品时,您只需为2种最昂贵的产品付款,同时还考虑了常规折扣。
实现可以如下:
public interface IHasProductPrice { decimal BasePrice { get; } decimal? SalePrice { get; } } public class Product: IHasProductPrice {
在写子系统中,惰性加载并不那么可怕
相反,在写入子系统中,通常仅用于写入的id是不够的。 各种检查通常会使您读取整个实体,因为对象范例涉及将数据和对它们的操作组合在类对象及其不变量内。 如果项目使用DDD,则必须通过聚合根目录执行写/更改操作,因此只能在一个对象及其依赖项上执行。 仅当使用相关集合时,才会发生大量查询。
集合中的相关集合
如果机器中的数据太多,则可能表明设计有问题。 聚合的典型根源-购物篮,订单,包裹。 人们通常不使用成千上万行的数据,因此下载整个链接的集合可能不是最有效的方法,但也不是致命的操作。 但是,如果集合中有成千上万个对象,则可能确实没有这样的聚合根,开发人员想出了它,因为使用即兴工具进行此操作非常简单。
如果汇总中仍然有成千上万条记录怎么办?
将
DbContext
传递给
构造函数,并仅从其读取操作上下文中所需的数据。 是的,违反DIP。 或者,或者在这种情况下完全不使用本机。
大规模运营
对于延迟加载,导入10,000行文件是一个很好的目标。 在这里,对于读取子系统的所有问题,还添加了ChangeTracker的制动器。 要进行批量录制,您需要使用单独的
工具 。 我更喜欢批处理扩展,因为您可以再次创建而不用创建实体。 对于特别严重的情况,有很好的旧存储过程,甚至还有
特殊的DBMS工具 。
生活骇客
如果需要同时执行大规模操作和常规操作,则需要从大规模操作开始。 正常操作只是在只有一个元素的序列中大量代码的一种特殊情况。