简体   繁体   English

在服务层中更新模型,而无需在MVVM中读取整个实体

[英]Update Model in Service Layer without reading the whole entity in MVVM

As known, the service layer is responsible for updating (and of course reading, writing and deleting) the models (or I may call them entities). 众所周知,服务层负责更新(当然还有读取,写入和删除)模型(或者我可以称它们为实体)。 Let us ignore Repository layer for now, because I do not like it, it just hides a lot of Entity Framework features. 现在让我们忽略存储库层,因为我不喜欢它,它只是隐藏了许多实体框架功能。

The flow of data in a well designed system should be: 设计良好的系统中的数据流应为:

Service (model) <<<-->>> Controller (mapping Model <-> ViewModel)

To be able to update the Model in the database by the above data flow, I have to read the whole model from the database to the service to the controller, and use AutoMapper (for example) to map the data I got from the ViewModel to the Model (now we have the old model with some changed values). 为了能够通过上述数据流更新数据库中的Model,我必须从数据库读取整个模型,再到服务再到控制器,然后使用AutoMapper(例如)将我从ViewModel获得的数据映射到模型(现在我们有了带有一些已更改值的旧模型)。 Now I can pass that edited Model back to the Service, and execute DbContext.Update(Model) . 现在,我可以将编辑后的Model传递回Service,并执行DbContext.Update(Model)

The cons of that is: 缺点是:

  • We had to issue additional read query to read the whole model (We had to do that, otherwise, DbContext.Update(Model) will leave the none mapped Property to default). 我们必须发出附加的读取查询来读取整个模型(我们必须这样做,否则DbContext.Update(Model)会将未映射的属性保留为默认值)。

  • Also, Update query has been generated for the whole model (although I might only changed small part of the Model.) 此外,已经为整个模型生成了更新查询(尽管我可能只更改了模型的一小部分。)

I sometimes find that design patterns forces to hide a lot of feature which may make the program more efficient. 我有时会发现设计模式迫使隐藏许多功能,这可能会使程序更高效。

Is there any approach (or let us say a design pattern or any editing to service pattern) where I can map ViewModel to Model, and pass the Model to the Service, and update only the mapped Properties (so there is no need to read the whole entity before mapping the properties)? 是否有任何方法(或让我们说一个设计模式或对服务模式进行任何编辑)可以将ViewModel映射到Model,然后将Model传递给Service,并且仅更新映射的Properties(因此无需阅读整个实体,然后再映射属性)?

My answer could be a little bit off topic, but I'm posting it anyway, because I feel like what I want to say it's a little to broad topic for comment. 我的回答可能与主题略有出入,但无论如何我都将其发布了,因为我觉得我想说的是一个有点宽泛的话题以供评论。

You say 你说

As known, the service layer is responsible for updating (and of course reading, writing and deleting) the models (or I may call them entities). 众所周知,服务层负责更新(当然还有读取,写入和删除)模型(或者我可以称它们为实体)。

The thing is, that service layer shouldn't actually be used like this. 事实是,实际上不应像这样使用服务层。 I mean - you should not have so called services at all. 我的意思是-您根本不应该拥有所谓的服务。 What you're describing is known as anemic domain model . 您所描述的被称为贫血域模型 It's combination of entities and services, where entities are simple data structures (not proper objects!) and services are used to perform operation on top of entity. 它是实体和服务的组合,其中实体是简单的数据结构(不是正确的对象!),服务用于在实体之上执行操作。

In Martin Fowler's words 用马丁·福勒的话

[...] there are a set of service objects which capture all the domain logic, carrying out all the computation and updating the model objects with the results. [...]有一组服务对象,它们捕获所有域逻辑,执行所有计算并使用结果更新模型对象。 These services live on top of the domain model and use the domain model for data. 这些服务位于域模型之上,并使用域模型存储数据。

And this is bad. 这很不好。

Combine methods and data inside one object. 将方法和数据组合一个对象中。 Then what you will be looking for would be Unit of Work design pattern. 然后,您将要寻找的是工作单元设计模式。 In fact Entity Framework already implements this pattern. 实际上,实体框架已经实现了这种模式。 As for implementing this rich-objects approach with EF (or actually any ORM you can use in .NET for that matter) I recommend Vaughn Vernon's post about designing aggregates with EF . 至于使用EF(或实际上可以在.NET中使用的任何ORM)实现这种丰富对象方法的方法,我建议沃恩·弗农(Vaughn Vernon)发表关于使用EF设计聚合的文章。 It's impossible to summarize this whole post, but in order to avoid link-only answer: basically Vaughn proposes two methods: creating Separated Interface with an implementation class and domain object being backed-up by something he calls state object. 不可能对整个文章进行总结,但是为了避免仅给出链接的答案:基本上,沃恩提出了两种方法:创建带有实现类和域对象(由他称为状态对象的东西)备份的Separated Interface。

To address comments: Basically yes - suggestion is to bring data and methods together instead of having one so called entity (which is simple data structure used only for getting data from DB in most cases) and separate class called service, which performs actions upon entity. 要解决评论:基本上是-建议将数据和方法放在一起,而不是拥有一个所谓的实体(在大多数情况下,这种简单的数据结构仅用于从DB中获取数据)和一个单独的类(称为服务),该类对实体执行操作。

As good and fun idea this might be, this is still all theoretical, right? 作为一个好玩的主意,这仍然是理论上的吧? Second link is about putting this approach to work. 第二个环节是将这种方法付诸实践。 Naturally, when you would start to refactor your code pivoting from anemic domain model to rich domain model you would have to ask yourself a question - how do I store my rich object in database? 自然地,当您开始重构从贫瘠的域模型转换为富域模型的代码时,您将不得不问自己一个问题-如何将富对象存储在数据库中? I mean - whole purpose of this is to encapsulate business (domain) logic inside object without having to deal with all this technical clutter. 我的意思是-这样做的整个目的是将业务(域)逻辑封装在对象内部,而不必处理所有这些技术问题。 We aim for as pure C# object as possible meaning - with as less dependencies as possible. 我们的目标是尽可能地实现纯C#对象的含义-减少依赖。 Let's talk examples. 让我们来谈谈例子。 Imagine you have Order and Product class. 假设您有OrderProduct类。

[Table("orders")]
class Order
{
    [Key]
    public int Id { get; set; }
    public List<Product> Products { get; set; }
    // rest omitted for clarity
}

[Table("products")]
class Product
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    // rest omitted for clarity
}

With anemic domain model you would have service performing actions like 使用贫血域模型,您将让服务执行诸如

class OrderService
{
    public void AddProduct(int orderId, int productId)
    {
        Order order = this.orderRepository.FirstOrDefault(orderId);
        Product product = this.productRepository.FirstOrDefault(productId);

        if(!order.Products.Contains(product)
        {
            order.Products.Add(product);
        }

        this.orderRepository.Save(order);
    }
}

With rich domain model you would have class like this 使用丰富的域模型,您将拥有像这样的课程

[Table("orders")]
class Order
{
    [Key]
    public int Id { get; set; }
    public List<Product> Products { get; set; }

    public bool AddProduct(Product product)
    {
        if(this.Products.Contains(product))
        {
            return false;
        }

        order.Products.Add(product);

        return true;
    }

}

This class would be used in controller like 此类将用于控制器

class OrderController
{
    public ActionResult AddProduct(int orderId, int productId){
        Order order = this.orderRepository.FirstOrDefault(orderId);
        Product product = this.productRepository.FirstOrDefault(productId);

        bool productAdded = order.AddProduct(product);

        // do something else
    }
}

Note two things: 1. We moved technical stuff to controller and encapsulated business logic inside Order class (let's pretend if(this.Products.Contains(product) is our only business rule) [good thing] 1. Order class should have only business-related stuff. ORM-specific annotations are introducing nothing but technical noise when it comes to reading and understanding this class, not to mention we have unnecessary dependencies inside our model [bad thing] 注意两件事:1.我们将技术内容转移到控制器,并将业务逻辑封装在Order类内(让我们假装if(this.Products.Contains(product)是我们唯一的业务规则))[好东西] 1. Order类应该只有业务与ORM有关的注解在阅读和理解此类时只会引入技术上的噪音,更不用说我们模型中有不必要的依赖项[不好的东西]

Vaughn discusses two ways of dealing with this. 沃恩讨论了两种解决方法。 You either extract separated interface with your business methods 您可以使用业务方法提取单独的界面

interface IOrder
{
    bool AddProduct(Product product);
}

And then implement it in your class like class Order : IOrder . 然后在您的类(例如class Order : IOrder实现它。 Drawbacks of this solution are pointed out in linked blog post 链接的博客文章中指出了该解决方案的缺点

The Ubiquitous Language is not really reinforced by using interfaces such as IProduct, IBacklogItem, etc. IProduct and IBacklogItem are not in our Ubiquitous Language, but Product and BacklogItem are. 使用IProduct,IBacklogItem等接口并不能真正增强Ubiquitous语言。IProduct和IBacklogItem不在我们的Ubiquitous语言中,而Product和BacklogItem在其中。 Thus, the client facing names should be Product, BacklogItem, and the like. 因此,面向客户的名称应该是Product,BacklogItem等。 We could accomplish this simply by naming the interfaces Product, BacklogItem, Release, and Sprint, but that would mean we would have to come up with sensible names for the implementation classes. 我们可以简单地通过命名接口Product,BacklogItem,Release和Sprint来完成此操作,但这意味着我们必须为实现类提供一个合理的名称。 Let's just pause there and move on to the second and related issue. 让我们暂停一下,继续讨论第二个相关问题。

and

There is really no good reason to create a Separated Interface. 确实没有充分的理由创建单独的接口。 It would be very unlikely that we would ever create two or more implementations of IProduct or any of the other interfaces. 我们不可能创建IProduct的两个或多个实现或任何其他接口。 The best reason we have for creating a Separated Interface is when there could be or are multiple implementations, and is just not going to happen in this Core Domain. 我们创建分离接口的最佳理由是,可能存在或存在多个实现,而在该核心域中不会发生。

Other solution is creating two objects - one for business logic only and other responsible for change tracking and used to communicate with database. 另一种解决方案是创建两个对象-一个仅用于业务逻辑,另一个负责更改跟踪并用于与数据库通信。 With this approach you have business object without any signs of irrelevant from business point of view details. 通过这种方法,您可以从业务角度来看业务对象,而没有任何无关紧要的迹象。 As Vaughn concludes 沃恩总结

In the end our goal is to stay out of the way of Entity Framework 最后,我们的目标是远离实体框架

This is really broad subject. 这确实是广泛的主题。 It's hard to try to explain it in SO fashion - there are whole books about this topic ;-). 很难尝试以SO方式解释它-整本关于这个主题的书;-)。 Generally I would recommend you reading Vernon's book titled Implementing Domain-Driven Design . 通常,我建议您阅读Vernon的书《 实现域驱动的设计》 It's about DDD, but it also shows how you can write proper object-oriented code. 它与DDD有关,但也显示了如何编写适当的面向对象的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM