C# 中的可查询性和延迟加载是否模糊了数据访问与业务逻辑的界限?

发布于 2024-09-25 11:22:53 字数 1516 浏览 6 评论 0原文

我正在经历职业生涯中期的哲学建筑危机。我看到客户端代码(UI、Web 服务、MVC、MVP 等)和服务层之间有非常清晰的界限。然而,服务层后面的界限正变得越来越模糊。这一切都始于使用 Linq 查询代码的能力和延迟加载的概念。

我创建了一个由合同和实现组成的业务层。然后,实现可以依赖于其他合同等等。这是通过带有 DI 的 IoC 容器来处理的。有一个服务处理数据访问,它所做的只是返回一个工作单元。此 UnitOfWork 在扩展时创建一个事务,并在 Commit 方法上提交数据。 [查看本文(可测试性和实体框架 4.0)]:

public interface IUnitOfWork : IDisposable {
   IRepository<T> GetRepository<T>() where T : class;
   void Commit();
}

存储库是通用的,适用于两种实现(EF4 和 InMemory DataStore)。 T 由从数据库架构或 EF4 映射生成的 POCO 组成。可测试性内置于存储库设计中。我们可以利用内存中的实现来断言结果符合预期。

public interface IRepository<T> where T : class {
   IQueryable<T> Table { get; }
   void Add(T entity);
   void Remove(T entity);
}

虽然数据源是抽象的,但 IQueryable 仍然使我能够在业务逻辑中的任何位置创建查询。这是一个例子。

public interface IFoo {
   Bar[] GetAll();
}

public class FooImpl : IFoo {
   IDataAccess _dataAccess;
   public FooImpl(IDataAccess dataAccess) {
      _dataAccess = dataAccess;
   }

   public Bar[] GetAll() {
      Bar[] output;
      using (var work = _dataAccess.DoWork()) {
          output = work.GetRepository<Bar>().Table.ToArray();
      }
      return output;
   }
}

现在您可以看到,当您使用复杂的过滤器执行联接时,查询如何变得更加复杂。

因此,我的问题是:

  1. BLL 和 DAL 之间没有明确的区别有关系吗?
  2. 当存储库层后面的行为类似于 InMemory 抽象时,可查询性是否被视为数据访问或业务逻辑?

补充:我想得越多,也许第二个问题是唯一应该问的问题。

I am experiencing a mid-career philosophical architectural crisis. I see the very clear lines between what is considered client code (UI, Web Services, MVC, MVP, etc) and the Service Layer. The lines from the Service layer back, though, are getting more blurred by the minute. And it all started with the ability to query code with Linq and the concept of Lazy loading.

I have created a Business Layer that consists of Contracts and Implementations. The Implementations then could have dependencies to other Contracts and so on. This is handled via an IoC Container with DI. There is one service that handles the DataAccess and all it does is return a UnitOfWork. This UnitOfWork creates a transaction when extantiated and commits the data on the Commit method. [View this Article (Testability and Entity Framework 4.0)]:

public interface IUnitOfWork : IDisposable {
   IRepository<T> GetRepository<T>() where T : class;
   void Commit();
}

The Repository is generic and works against two implementations (EF4 and an InMemory DataStore). T is made up of POCOs that get generated from the database schema or the EF4 mappings. Testability is built into the Repository design. We can leverage the in-memory implementation to assert results with expectations.

public interface IRepository<T> where T : class {
   IQueryable<T> Table { get; }
   void Add(T entity);
   void Remove(T entity);
}

While the Data Source is abstracted, IQueryable still gives me the ability to create queries anywhere I want within the Business logic. Here is an example.

public interface IFoo {
   Bar[] GetAll();
}

public class FooImpl : IFoo {
   IDataAccess _dataAccess;
   public FooImpl(IDataAccess dataAccess) {
      _dataAccess = dataAccess;
   }

   public Bar[] GetAll() {
      Bar[] output;
      using (var work = _dataAccess.DoWork()) {
          output = work.GetRepository<Bar>().Table.ToArray();
      }
      return output;
   }
}

Now you can see how the queries could get even more complex as you perform joins with complex filters.

Therefore, my questions are:

  1. Does it matter that there is no clear distinction between BLL and the DAL?.
  2. Is queryability considered data access or business logic when behind a Repository layer that acts like an InMemory abstraction?

Addition: The more I think about it, maybe the second question was the only one that should have been asked.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

葬﹪忆之殇 2024-10-02 11:22:53

我认为回答您的问题的最佳方法是退后一步,考虑为什么业务逻辑层和数据访问层之间的分离是推荐的做法。

在我看来,原因很简单:将业务逻辑与数据层分开,因为业务逻辑是价值所在,数据层和业务逻辑需要随着时间的推移或多或少彼此独立地进行更改,并且业务逻辑需要可读,而不必详细了解所有数据访问层的功能。

因此,查询体操的试金石可以归结为:

  1. 您能否在不扰乱大部分业务逻辑的情况下对系统中的数据模式进行更改?
  2. 您和其他 C# 开发人员可以阅读您的业务逻辑吗?

I think the best way to answer your questions is to step back a moment and consider why separation between business logic layers and data access layers is the recommended practice.

In my mind, the reasons are simple: keep the business logic separate from the data layer because the business logic is where the value is, the data layer and business logic will need to change over time more or less independently of each other, and and the business logic needs to be readable without having to have detailed knowledge of what all the data access layer does.

So the litmus test for your query gymnastics boils down to this:

  1. Can you make a change to the data schema in your system without upsetting a significant portion of the business logic?
  2. Is your business logic readable to you and to other C# developers?
南烟 2024-10-02 11:22:53

1.仅当您更关心哲学而不是完成工作时。 :)

2. 我认为这是业务逻辑,因为两者之间有一个抽象。我将该存储库层称为 DAL 的一部分,以及使用它的任何内容,BL。

但是,是的,这对我来说也很模糊。不过我认为这并不重要。使用这样的模式的目的是编写一个干净、可用、同时易于通信的代码,并且无论哪种方式都可以实现该目标。

1. Only if you care more about philosophy than getting stuff done. :)

2. I'd say it's business logic because you have an abstraction in between. I would call that repository layer part of DAL, and anything that uses it, BL.

But yeah, this is blurry to me as well. I don't think it matters though. The point of using patterns like this is to write a clean, usable code that easy to communicate at the same time, and that goal is accomplished either way.

不寐倦长更 2024-10-02 11:22:53

1.BLL 和 DAL 之间没有明确的区别有关系吗?

这确实很重要!任何使用 Table 属性的程序员都需要了解其后果(数据库往返、查询转换、对象跟踪)。这也适用于阅读业务逻辑类的程序员。

2.当存储库层后面的行为类似于 InMemory 抽象时,可查询性是否被视为数据访问或业务逻辑?

抽象是我们隐藏问题的毯子。

如果您的抽象是完美的,那么查询可以被抽象地视为针对内存中集合进行操作,因此它们不是数据访问。

然而,抽象泄漏了。如果您希望查询在数据世界中有意义,则必须努力超越抽象。这种额外的努力(破坏了抽象)产生了数据访问代码。


一些例子:

output = work.GetRepository<Bar>().Table.ToArray(); 

这是代码(抽象地)很好。但在数据世界中,它会导致扫描整个表,并且(至少一般来说)是愚蠢的!


badquery = work.GetRepository<Customer>().Table.Where(c => c.Name.Contains("Bob")).ToArray(); 
goodquery = work.GetRepository<Customer>().Table.Where(c => c.Name.StartsWith("Bob")).ToArray(); 

Customer.Name 上有索引时,好的查询比坏的查询要好。但除非我们解除抽象,否则我们无法获得这一事实。


badquery = work.GetRepository<Customer>().Table
  .GroupBy(c => c.Orders.Count())
  .Select(g => new
  {
    TheCount = g.Key,
    TheCustomers = g.ToList()
  }).ToArray();
goodquery = work.GetRepository<Customer>().Table
  .Select(c => new {Customer = c, theCount = c.Orders.Count())
  .ToArray()
  .GroupBy(x => x.theCount)
  .Select(g => new
  {
    TheCount = g.Key,
    TheCustomers = g.Select(x => x.Customer).ToList()
  })
  .ToArray();

goodquery 比 bad 查询要好,因为 badquery 会针对每个组按组键重新查询数据库(更糟糕的是,不太可能有索引来帮助通过 c.Orders.Count() 过滤客户) > ).


可测试性内置于存储库设计中。我们可以利用 InMemory 实现来断言符合预期的结果。

如果您实际针对内存中集合运行查询,请不要抱有任何幻想,您的查询正在接受测试。除非涉及数据库,否则这些查询是无法测试的。

1.Does it matter that there is no clear distinction between BLL and the DAL?.

It sure does matter! Any programmer that uses your Table property needs to understand the ramifications (database roundtrip, query translation, object tracking). That goes for programmers reading the business logic classes as well.

2.Is queryability considered data access or business logic when behind a Repository layer that acts like an InMemory abstraction?

Abstraction is a blanket that we hide our problems under.

If your abstraction is perfect, then the queries could be abstractly considered as operating against in-memory collections and therefore they are not data access.

However, abstractions leak. If you want queries that make sense in the data world, there must be effort to work above and beyond the abstraction. That extra effort (which defeats abstraction) produces data access code.


Some examples:

output = work.GetRepository<Bar>().Table.ToArray(); 

This is code is (abstractly) fine. But in the data world it results in scanning an entire table and is (at least generally) dumb!


badquery = work.GetRepository<Customer>().Table.Where(c => c.Name.Contains("Bob")).ToArray(); 
goodquery = work.GetRepository<Customer>().Table.Where(c => c.Name.StartsWith("Bob")).ToArray(); 

Goodquery is better than bad query when there's an index on Customer.Name. But that fact is not available to us unless we lift the abstraction.


badquery = work.GetRepository<Customer>().Table
  .GroupBy(c => c.Orders.Count())
  .Select(g => new
  {
    TheCount = g.Key,
    TheCustomers = g.ToList()
  }).ToArray();
goodquery = work.GetRepository<Customer>().Table
  .Select(c => new {Customer = c, theCount = c.Orders.Count())
  .ToArray()
  .GroupBy(x => x.theCount)
  .Select(g => new
  {
    TheCount = g.Key,
    TheCustomers = g.Select(x => x.Customer).ToList()
  })
  .ToArray();

goodquery is better than bad query since badquery will requery the database by group key, for each group (and worse, it is highly unlikely there is an index to help with filtering customers by c.Orders.Count() ).


Testability is built into the Repository design. We can leverage the InMemory implementation to assert results with expectations.

Be under no illusions that your queries are being tested if you actually run them against in-memory collections. Those queries are untestable unless a database is involved.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文