LINQ toEntity - 传递模型的最佳实践

发布于 2024-09-09 05:47:30 字数 1212 浏览 6 评论 0原文

众所周知,大多数应用程序都有一个数据访问层,通常使用存储库类。通常,我们希望存储库能够使用强类型对象进行操作,例如,

interface IUserRespository
{
     User GetUserByID(int id);
     void AddUser(User u);
     ... and so on
}

但是,有时我们希望对数据库进行更复杂的查询,这涉及分组和聚合。例如,我们要获取所有用户的所有订单的总值(结果集只有两列:UserName 和 TotalAmount)。现在我的问题:存储库方法是什么样的? LINQ toEntity 网上有很多关于如何使用 sum 和 group by 进行查询的示例,但所有这些示例都返回匿名类型。那么,我们的存储库如何返回此类查询的结果呢?另一个常见的答案是:将其包装在一个类中。因此该方法可能如下所示:

interface IUserRepository
{
    UserOrderTotal[] GetOrderTotalsForAllUsers();
}

UserOrderTotal 必须是一个小类,具有查询返回的两个属性:UserName 和 TotalAmount。该类将在我们的 .cs 文件之一中的某个位置定义。这真的是唯一的解决方案吗?它看起来非常糟糕,因为它在主要实体模型(edmx)之外的某个地方引入了一个新的“临时”模型。理想情况下,我想用一个名为 TotalAmount 的新字段来丰富我的 User 模型,该字段仅在此查询中填充,因此界面可能如下所示:

interface IUserRepository
{
    User[] GetOrderTotalsForAllUsers();
}

它将返回所有字段设置为默认值的 User 实体,除了名称和总金额。我遇到的问题是,我无法编译具有未映射到数据库列的字段的模型。我收到错误 错误 3004:从第 2226 行开始的映射片段出现问题:没有为 Set User 中的属性 User.TotalAmount 指定映射。当以下情况时,带有密钥 (PK) 的实体将不会往返:实体类型为 [FPSoMeterModel.User ]

我做错了吗?这个问题是微不足道的,还是我问了错误的问题?为每个涉及聚合的查询(!)创建一个包装类似乎有点荒谬。看起来整个 LINQ 的东西鼓励人们不要使用多层架构,而是在呈现数据的同一层中构建查询......你们如何处理这个问题,你们是否使用“存储库”类LINQ,如果是的话 - 如何返回复杂的查询结果?

As we all know most apps have a data access layer, often using repository classes. Typically we want the repository to operate with strongly typed objects, e.g.

interface IUserRespository
{
     User GetUserByID(int id);
     void AddUser(User u);
     ... and so on
}

However, sometimes we want to make a more complex query on a DB, that involves grouping and aggregation. For instance we want fetch a total value of all orders of all users (the result set will have only two columns: UserName and TotalAmount). Now my question: what would the repository method look like? There are dozens of examples on the internet of LINQ to entities how to make a query with sum and group by, but all these examples return anonymous types. So, how can our repository return the result of such query? The other common answer is: wrap it in a class. So the method could look like this:

interface IUserRepository
{
    UserOrderTotal[] GetOrderTotalsForAllUsers();
}

UserOrderTotal would have to be a small class with the two properties returned by the query: UserName and TotalAmount. This class would be defined somewhere in one of our .cs files. Is this really the only solution? It looks very bad because it introduces a new 'temporary' model somewhere outside the main entities model (edmx). Ideally I'd like to enrich my User model with a new field, called TotalAmount, that would only be populated in this query, so the interface could look like this:

interface IUserRepository
{
    User[] GetOrderTotalsForAllUsers();
}

And it would return User entities with all fields set to default, except the Name and TotalAmount. The problem that I have is that I cannot compile a model with field that is not mapped to a database column. I'm getting error Error 3004: Problem in mapping fragments starting at line 2226:No mapping specified for properties User.TotalAmount in Set User.An Entity with Key (PK) will not round-trip when: Entity is type [FPSoMeterModel.User]

Am I doing it wrong? Is this problem trivial, or maybe I'm asking wrong questions? Making a wrapper class for each query (!) that involves aggregation seems a bit ridiculous. It seems that the whole LINQ stuff encourage people NOT to use a multi-tier architecture, but to build queries in the same layer where the data is rendered... How do you guys deal with this, do you use "repository" classes with LINQ, if yes - how do you return complex query results?

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

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

发布评论

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

评论(1

不打扰别人 2024-09-16 05:47:30

相反,我不认为定义新的 UserOrderTotal 类是不好的做法。有两种看待这样一个物体的方法。

(1) 您可以将此 UserOrderTotal 类视为数据传输对象(DTO)。这样它就不是您的域的一部分。 DTO 通常用于通过线路传输实体或以扁平形式向表示层提供域对象。我一直在使用它们。在这种情况下,对象被用作数据上的投影:作为视图。 MVVM 模式使用相同的原理,其中 MVVM 的 ViewModel 部分是用户界面特定的 DTO。

(2) 您可以将此 UserOrderTotal 类视为域的一部分。从领域驱动设计的角度来看,对象可以是用户语言的一部分。在这种情况下,将其定义为域对象是合乎逻辑的结果。但不同之处在于,它不会在实体框架 (EF) 的 edmx 文件中定义。我认为在 EF 中可以定义没有映射到数据库表的实体,但我不知道这是否会让事情变得更容易。我个人认为在 edmx 文件所在的同一项目中的 edmx 文件外部定义这些域对象没有问题。

关于返回 User 对象数组:这似乎是一个坏主意。这种方法存在几个问题。首先,您的代码没有很好地传达其意图。您使用 User 对象,但将所有属性留空。对于任何尝试使用此代码的人来说,这种做法很难遵循。实际上,您正在滥用 User 对象来实现它不是的东西:即聚合。

除此之外,您注意到 EF 无法处理您添加到模型中的这个额外属性。虽然通常可以向 EF 模型添加属性(通过使用部分类),但您对此应该非常保守。当您在 LINQ 查询中使用这些属性时,虽然可以编译,但在运行时会失败。

为每个查询创建一个包装类
(!) 涉及聚合似乎是
有点可笑。

我不这么认为。 IMO 这是正确的做法。这是在多层架构中使用 EF 的结果。我必须承认,在单层中使用 EF 会容易得多,但从长远来看,它会变得非常混乱。项目越大,需要维护的时间越长,添加抽象层就越合理。

你们是如何处理这个问题的?
将“存储库”类与 LINQ 结合使用

我设计的应用程序通常有一个服务/业务层,用于将读取与突变分开。对于读取操作/查询,我使用静态类以及返回域对象或 DTO 集合的方法。通常,这样的“服务方法”特定于用户界面的特定部分。这些突变包含在“服务命令”中。用户界面创建服务命令并触发它。命令表达单个用例或用户故事,并且是逻辑的原子部分。服务层将确保在执行命令时创建数据库事务(在需要时)。对于用户界面,使用命令通常如下所示:

void UpdateButton_Click(object sender, EventArgs e)
{
    if (!this.Page.IsValid)
    {
        return;
    }

    var command = new UpdateUserSettingsCommand();

    command.UserName = this.UserName.Text;
    command.MailAddress = this.MailAddress.Text;

    command.Execute();
}

我希望这一切都有意义。如果您想了解有关我如何使用 DTO 的更多信息,请阅读此所以我的回答

我希望这有帮助。

I wouldn't consider defining a new UserOrderTotal class bad practice, on the contrary. There are two ways of looking at such an object.

(1) You can see this UserOrderTotal class as an Data Transfer Object (DTO). In this way it is not part of your domain. DTOs are normally used for transferring entities over the wire or to supply domain objects in a flattened form to the presentation layer. I use them all the time. In this situation the object is used as a projection on the data: as a view. The MVVM pattern uses this same principle where the ViewModel part of MVVM is a user interface specific DTO.

(2) You can see this UserOrderTotal class as part of your domain. From a domain driven design perspective the object could be part of the users language. In that situation, it would be a logical consequence to define it as domain object. Difference however is that it will not be defined in de Entity Framework’s (EF) edmx file. I think it is possible in EF to define entities that don’t have a mapping to a database table, but I don’t know if that is making things easier. I personally don't see a problem defining these domain objects outside the edmx file in the same project as where the edmx is located.

About returning an array of User objects: This seems like a bad idea. There are several problems with that approach. First of all your code isn’t communicating its intent very well. You use a User object, but leave all properties empty. This practice is very hard to follow for anyone trying to use this code. You are actually abusing the User object for something it is not: namely an aggregate.

Besides this you noticed that EF can't handle this extra property you added to the model. While in general it is possible to add properties to EF model (by using partial classes) you should be very conservative about this. While things will compile things will fail at runtime when you use these properties in LINQ queries.

Making a wrapper class for each query
(!) that involves aggregation seems a
bit ridiculous.

I don’t think so. IMO it is the right thing to do. It is the consequence of using EF in a multi-layer architecture. I must admit that having EF in a single layer is much easier, but in the long turn it gets really messy. The bigger the project and the longer you need it to be maintained, the more adding layers of abstractions is justified.

How do you guys deal with this, do you
use "repository" classes with LINQ

The application I’ve designed, often have a service / business layer that separates the reads from the mutations. For read operations / queries I use static classes with methods that return a collection of domain objects or DTOs. Often such a 'service method' is specific to a particular part of the user interface. The mutations are wrapped within 'service commands'. The user interface creates a service command and fires it. A command expresses a single use case or user story and is an atomic piece of logic. The service layer will ensure the creation of a database transaction (when needed) when the command is executed. For the user interface, using a command usually looks like this:

void UpdateButton_Click(object sender, EventArgs e)
{
    if (!this.Page.IsValid)
    {
        return;
    }

    var command = new UpdateUserSettingsCommand();

    command.UserName = this.UserName.Text;
    command.MailAddress = this.MailAddress.Text;

    command.Execute();
}

I hope this all makes sense. If you want to read more about how I use DTOs, read this SO answer of mine.

I hope this helps.

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