在ORM层上创建一个抽象层


12

我相信,如果您的存储库使用的是ORM,那么它已经足够从数据库中提取出来了。

但是,在我现在工作的地方,有人认为我们应该有一个抽象ORM的层,以防我们以后要更改ORM。

创建一个可以在许多ORM上使用的层真的必要吗?

编辑

只是为了提供更多细节:

  1. 我们有与AutoMapper映射的POCO类和实体类。实体类由存储库层使用。然后,存储库层使用附加的抽象层与Entity Framework进行通信。
  2. 业务层决不能直接访问实体框架。即使在ORM上没有附加的抽象层,这一层也需要使用使用存储层的服务层。在这两种情况下,业务层都与ORM完全分开。
  3. 主要论据是将来能够更改ORM。因为对我来说,它确实是本地化的,所以它已经很好地分离了,我不明白为什么要使用一个附加的抽象层才能具有“质量”代码。

3
额外的层需要证明,否则会违反YAGNI。换句话说,某人认为您需要它,有负担去证明这一点
2012年

2
我可以理解,只需要一个仅暴露所需的操作子集的域层-ORM的表面积往往太宽(例如,您不希望允许对不由另一个包含实体指导的实体进行更新)。拥有这样的抽象层将对此有所帮助。
奥德

4
如果您也想更改第一层,则可能需要在ORM上方的第一层抽象中使用第二层抽象。
大卫·彼得曼

1
@David当我们添加冗余时,将所有的if(boolean)更改为if(boolean == true),如果您想对它们进行更多的反省,则if(boolean == true == true ...)以此类推
brian 2012年

Answers:


12

那就是疯狂。您极不可能需要更改ORM。而且,如果您决定更改ORM,则重写映射的成本将只是开发和维护自己的meta-ORM的成本的一小部分。我希望您可以编写一些脚本来完成切换ORM所需的95%的工作。

内部框架几乎总是灾难。预期将来的需求而建立一个几乎是可以保证的灾难。成功的框架是从成功的项目中提取的,而不是为满足想象的需求而提前构建的。


12

ORM为您的数据层提供了一个独立于其RDBMS的抽象,但可能不足以将您的业务层与数据层“分离”。具体来说,您不应让映射到RDBMS表的对象直接“泄漏”到业务层。

至少,您的业务层需要进行编程以编程接口,以便数据层中ORM管理的表映射对象可以实现。此外,您可能需要创建一个基于接口的抽象查询构建层,以隐藏ORM的本机查询功能。主要目标是避免将任何特定的ORM“烘焙”到解决方案的数据层之外。例如,可能很想在业务层中创建HQL(休眠查询语言)字符串。但是,这个看似无辜的决定会使您的业务层与Hibernate绑定在一起,从而将业务层和数据访问层固定在一起。您应尽量避免这种情况。

编辑:在您的情况下,存储库中的附加层是浪费时间:基于第二点,您的业务层与存储库已充分隔离。提供额外的绝缘将引入不必要的复杂性,而没有额外的好处。

在存储库内部构建额外的抽象层的问题在于,ORM的特定“品牌”决定了您与之交互的方式。如果您构建一个看起来像您的ORM但受您控制的瘦包装器,则替换基础ORM的难度将与没有该附加层的情况大致相同。另一方面,如果您构建的层看起来不像您的ORM,那么您应该质疑对对象关系映射技术的选择。


我很高兴.NET通过将查询对象烘焙到平台中解决了这个问题。Hibernate的.NET端口甚至支持它。
迈克尔·布朗

2
@MikeBrown是的,.NET还使用LINQ技术提供了自己的两种竞争ORM技术!
dasblinkenlight 2012年

@dasblinkenlight我更新了问题以为您提供更多信息。
Patrick Desjardins,2012年

最近,我采用了使业务层依赖于基于类似于地图和类似于集合的接口的数据层来存储状态的方法。我现在认为这些接口已经很好地表示了状态维护问题(受编程功能风格的启发),并且通过相当薄的实现与选择的ORM很好地隔离了。
beluchin

2

UnitOfWork通常提供这种抽象。这是一个需要更改的地方,您的存储库通过接口依赖它。如果您需要更改O / RM,只需对其实施新的UoW。一劳永逸。

顺便说一句,它不仅仅是切换O / RM,还可以考虑单元测试。我有三种UnitOfWork实施,一种用于EF,一种用于NH(因为我实际上DID必须为需要Oracle支持的客户端切换项目中的O / RM),以及一种用于InMemory持久性。InMemory持久性非常适合在我准备将数据库置于其后的单元测试或快速原型制作中。

该框架易于实现。首先,您具有通用的IRepository接口

public interface IRepository<T>
  where T:class
{
  IQueryable<T> FindBy(Expression<Func<T,Bool>>filter);
  IQueryable<T> GetAll();
  T FindSingle(Expression<Func<T,Bool>> filter);
  void Add(T item);
  void Remove(T item);

}

和IUnitOfWork接口

public interface IUnitOfWork
{
   IQueryable<T> GetSet<T>();
   void Save();
   void Add<T>(T item) where T:class;
   void Remove<T>(T item) where T:class;
}

接下来是基础存储库(您是否应该选择抽象存储库的选择

public abstract class RepositoryBase<T>:IRepository<T>
  where T:class
{
   protected readonly IUnitOfWork _uow;

   protected RepositoryBase(IUnitOfWork uow)
   { 
      _uow=uow;
   }

   public IQueryable<T> FindBy(Expression<Func<T,Bool>>filter)
   {
      return _uow.GetSet<T>().Where(filter);
   }

   public IQueryable<T> GetAll()
   {
      return _uow.GetSet<T>();
   }

   public T FindSingle(Expression<Func<T,Bool>> filter)
   {
      return _uow.GetSet<T>().SingleOrDefault(filter);
   }

   public void Add(T item)
   {
      return _uow.Add(item);
   }

   public void Remove(T item)
   {
      return _uow.Remove(item);
   }
}

实施IUnitOfWork是EF,NH和In Memory的小事。我返回IQueryable的原因是相同的,Ayende在他的帖子中提到,客户端可以使用LINQ进一步过滤,排序,分组甚至投影结果,您仍然可以从服务器端受益。


1
但是这里的问题是确定上面的那一层是否有用,并且应该成为所有数据访问的看门人。
2012年

希望我可以指向我关于工作单元/存储库实现的博客文章。它讨论了OP的确切问题。
迈克尔·布朗

给图层起一个名字并不意味着它是必要的或有用的。
凯文·克莱恩

请注意,根据OP,他在数据访问和业务层之间有一个额外的映射。对我来说,我的业务对象和实体对象是相同的。EF和NH提供了惊人的映射API,因此很少(如果有)数据映射成为一个问题。
迈克尔·布朗

您如何将任意表达式转换为有效的ORM调用?您不能只获取所有内容并丢弃与过滤器不匹配的行。
凯文·克莱恩
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.