.net ORM,使我无需修改即可扩展代码(开放式封闭原则)

时间:2018-10-29 22:44:50

标签: c# .net linq orm open-closed-principle

免责声明:以下是我对问题的非常简单的描述。请在阅读它时,想象一下一些复杂的模块化应用程序(ERP,Visual Studio,Adobe Photoshop),这些应用程序在过去的几年中不断发展,其功能将被添加和删除,以希望它不会最终成为意大利面条式代码。

假设我在数据库中有以下实体和相应的表

class Customer
{
    public int Id { get; set; }
}

然后我将使用一些ORM,构建DataContext并创建我的应用程序

static void Main(string[] args)
{
    //Data Layer
    var context = new DataContext();
    IQueryable<Customer> customers = context.Customers;

    //GUI
    foreach (var customer in customers)
    foreach (var property in customer.GetType().GetProperties())
        Console.WriteLine($"{property.Name}:{property.GetValue(customer)}");
}

申请完成,我的客户满意,结案。

半年后,我的客户要求我为客户添加名称。 我想做到而无需触及先前的代码。

因此,首先,我将在数据库中创建新的实体和对应的表,并将其添加到ORM中(请忽略我正在修改同一DataContext的事实,这很容易解决)

class CustomerName
{
    public int Id { get; set; }
    public string Name { get; set; }
}

CustomerName将为客户添加新属性,但是要获得完整的客户信息,我们需要将它们结合在一起,所以让我们尝试修改我们的应用程序而无需触及先前的代码

static void Main(string[] args)
{
    //Data Layer
    var context = new DataContext();
    IQueryable<Customer> customers = context.Customers;

     //new code that doesn't even compile
    customers = from c in customers
                join cn in context.CustomerNames on c.Id equals cn.Id
                select new {c, cn}; //<-- what should be here??

    //GUI
    foreach (var customer in customers)
    foreach (var property in customer.GetType().GetProperties())
        Console.WriteLine($"{property.Name}:{property.GetValue(customer)}");
}

您可以看到,我不知道该如何映射联接中的信息,以便它仍然是有效的Customer对象。

不,我不能使用继承。

为什么?

因为同时可能会要求另一个开发人员提供阻止客户并创建以下实体的功能:

class BlockedCustomer
{
    public int Id { get; set; }
    public bool Blocked { get; set; }
}

他对客户名称一无所知,因此他可能仅依赖于客户,并且在运行时,我们的两种功能都将导致以下情况:

static void Main(string[] args)
{
    //Data Layer
    var context = new DataContext();
    IQueryable<Customer> customers = context.Customers;

     //new code that doesn't even compile
    customers = from c in customers
                join cn in context.CustomerNames on c.Id equals cn.Id
                select new {c, cn}; //<-- what should be here??

    customers = from c in customers
        join b in context.BlockedCustomers on c.Id equals b.Id
        select new { c, b }; //<-- what should be here??

            //GUI
            foreach (var customer in customers)
    foreach (var property in customer.GetType().GetProperties())
        Console.WriteLine($"{property.Name}:{property.GetValue(customer)}");
}

我有2个解决方法

  1. 创建一些将继承自Customer的容器类,并在需要时将其强制转换/转换为CustomerName或BlockedCustomer。

类似这样的东西:

class CustomerWith<T> : Customer
{
    private T Value;

    public CustomerWith(Customer c, T value) : base(c)
    {
        Value = value;
    }
}

然后

customers = from c in customers
            join cn in context.CustomerNames on c.Id equals cn.Id
            select new CustomerWith<CustomerName>(c, cn);
  1. 使用ConditionalWeakTable存储(在数据层级别)与Customer相关联的CustomerName和BlockedCustomer并修改(一次)UI以了解此类情况

据我所知,不幸的是,这两种解决方案都要求我编写自己的LINQ映射器(包括更改跟踪),而我想避免使用它。

  1. 您知道任何知道如何处理此类要求的ORM吗?
  2. 或者也许有更好/更简单的解决方案来编写应用程序并且不违反开放/封闭原则?

编辑-评论后的一些说明:

  1. 我说的是与客户具有一对一关系的属性。通常,将这些属性添加为表中的其他列。
  2. 我只想向数据库发送一个SQL查询。因此,添加20个这样的新功能/属性/列不应最终出现在20个查询中。
  3. 我展示的是我想到的应用程序的简化版本。我在考虑应按以下方式构造依赖项的应用程序[UI]-> [Business Logic] <-[Data]。所有3个都应打开以进行扩展,并关闭以进行修改,但在这个问题上我将重点放在[Data]层上。 [业务逻辑]将向[数据]层(使用Linq)询问客户。因此,即使在扩展了它之后,客户BL仍会(在Linq中)要求客户,但是对[业务逻辑]的扩展将需要客户名称。问题是如何扩展[数据]层,以便它仍然可以将Customer返回给Customer BL,但是将CustomerName提供给Customer name BL并仍然向数据库发送一个查询。
  4. 当我将Joins作为建议的解决方案显示时,可能不清楚它们是否不会在[Data]层中进行硬编码,但是[Data]层应该知道它应该调用一些可能想要扩展该方法的方法。查询,并将由main()模块注册。 Main()模块是唯一了解所有依赖关系的模块,对于此问题而言,如何操作并不重要。

1 个答案:

答案 0 :(得分:0)

  

我在数据库中有以下实体和相应的表

我认为这个基本前提是大多数问题的根本原因。

要清楚,将应用程序体系结构建立在基础关系数据库架构上并没有本质上的坏处。有时,所有利益相关者都需要一个简单的CRUD应用程序。

但是,重要的是要意识到,无论何时做出选择,应用程序体系结构都会固有地是关系的(即,不是面向对象的)。

完全规范化的关系数据库的特征是关系。这些表通过外键与其他表相关。这些关系由数据库引擎强制执行。通常,在完全规范化的数据库中,(几乎)所有表都(以传递方式)连接到所有其他表。换句话说,一切都已连接。

当事物连接在一起时,在编程中我们通常将其称为 coupling ,这是我们要避免的事情。

您想将应用程序体系结构划分为模块,但仍将其建立在所有事物都耦合在一起的数据库上。我不知道这样做的实际方法。我认为这是不可能的。

ORM只会使情况变得更糟。正如泰德·纽德(Ted Neward)十多年前教给我们的那样,ORMs is the Vietnam war of computer science。几年前,我已经放弃了它们,因为它们会使包括软件体系结构在内的所有方面变得更糟。

设计复杂的模块化系统的一种更有希望的方法是CQRS(例如,参见my own attempt at explaining the concept back in 2011)。

这将使您能够将域名事件(例如命名客户阻止客户)视为不仅封装 how ,还有为什么会发生什么事情。

那么,在读取侧,您可以通过(持久)事务数据投影提供数据。但是,在CQRS体系结构中,事务数据通常更适合事件源或文档数据库。

这种架构非常适合微服务,但是您也可以通过这种方式编写更大的模块化应用程序。

像OOD这样的关系数据库在一段时间内听起来像是个好主意,但最终我的经验是这两个主意都是死胡同。它们无助于我们降低复杂性。它们不会使事情更易于维护。