ASP.NET MVC 与EF4 实体框架 - 使用实体与仅检索我需要的字段相比是否存在任何性能问题?

发布于 2024-10-11 17:42:13 字数 3787 浏览 1 评论 0原文

假设我们有 3 个表:用户、产品、购买。 有一个视图需要显示用户所做的购买。

我可以通过以下方式查找所需的数据:

from p in DBSet<Purchases>.Include("User").Include("Product") select p;

但是,我担心这可能会对性能产生影响,因为它将检索完整的对象。 或者,我可以只选择我需要的字段:

from p in DBSet<Purchases>.Include("User").Include("Product") select new SimplePurchaseInfo() { UserName = p.User.name, Userid = p.User.Id, ProductName = p.Product.Name ... etc };

所以我的问题是: 这样做的最佳实践是什么?

== 编辑

感谢您的所有回复。

[问题 1]:我想知道是否所有视图都应该与具有该视图的非常具体数据的平面 ViewModel 一起使用,或者 ViewModel 是否应该包含实体对象。

真实示例:用户评论产品

var query = from dr in productRepository.FindAllReviews()
            where dr.User.UserId = 'userid'
            select dr;
string sql = ((ObjectQuery)query).ToTraceString();

SELECT [Extent1].[ProductId] AS [ProductId], 
       [Extent1].[Comment] AS [Comment], 
       [Extent1].[CreatedTime] AS [CreatedTime], 
       [Extent1].[Id] AS [Id], 
       [Extent1].[Rating] AS [Rating], 
       [Extent1].[UserId] AS [UserId], 
       [Extent3].[CreatedTime] AS [CreatedTime1], 
       [Extent3].[CreatorId] AS [CreatorId], 
       [Extent3].[Description] AS [Description], 
       [Extent3].[Id] AS [Id1], 
       [Extent3].[Name] AS [Name], 
       [Extent3].[Price] AS [Price], 
       [Extent3].[Rating] AS [Rating1], 
       [Extent3].[ShopId] AS [ShopId], 
       [Extent3].[Thumbnail] AS [Thumbnail], 
       [Extent3].[Creator_UserId] AS [Creator_UserId], 
       [Extent4].[Comment] AS [Comment1], 
       [Extent4].[DateCreated] AS [DateCreated], 
       [Extent4].[DateLastActivity] AS [DateLastActivity], 
       [Extent4].[DateLastLogin] AS [DateLastLogin], 
       [Extent4].[DateLastPasswordChange] AS [DateLastPasswordChange], 
       [Extent4].[Email] AS [Email], 
       [Extent4].[Enabled] AS [Enabled], 
       [Extent4].[PasswordHash] AS [PasswordHash], 
       [Extent4].[PasswordSalt] AS [PasswordSalt], 
       [Extent4].[ScreenName] AS [ScreenName], 
       [Extent4].[Thumbnail] AS [Thumbnail1], 
       [Extent4].[UserId] AS [UserId1], 
       [Extent4].[UserName] AS [UserName]
       FROM    [ProductReviews] AS [Extent1]
       INNER JOIN [Users] AS [Extent2] ON [Extent1].[UserId] = [Extent2].[UserId]
       LEFT OUTER JOIN [Products] AS [Extent3] ON [Extent1].[ProductId] = [Extent3].[Id]
       LEFT OUTER JOIN [Users] AS [Extent4] ON [Extent1].[UserId] = [Extent4].[UserId]
       WHERE N'615005822' = [Extent2].[UserId]

from d in productRepository.FindAllProducts()
from dr in d.ProductReviews
where dr.User.UserId == 'userid'
orderby dr.CreatedTime
select new ProductReviewInfo()
       {
           product = new SimpleProductInfo() { Id = d.Id, Name = d.Name, Thumbnail = d.Thumbnail, Rating = d.Rating },
           Rating = dr.Rating,
           Comment = dr.Comment,
           UserId = dr.UserId,
           UserScreenName = dr.User.ScreenName,
           UserThumbnail = dr.User.Thumbnail,
           CreateTime = dr.CreatedTime
       };

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
[Extent1].[Thumbnail] AS [Thumbnail], 
[Extent1].[Rating] AS [Rating], 
[Extent2].[Rating] AS [Rating1], 
[Extent2].[Comment] AS [Comment], 
[Extent2].[UserId] AS [UserId], 
[Extent4].[ScreenName] AS [ScreenName], 
[Extent4].[Thumbnail] AS [Thumbnail1], 
[Extent2].[CreatedTime] AS [CreatedTime]
FROM    [Products] AS [Extent1]
INNER JOIN [ProductReviews] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ProductId]
INNER JOIN [Users] AS [Extent3] ON [Extent2].[UserId] = [Extent3].[UserId]
LEFT OUTER JOIN [Users] AS [Extent4] ON [Extent2].[UserId] = [Extent4].[UserId]
WHERE N'userid' = [Extent3].[UserId]
ORDER BY [Extent2].[CreatedTime] ASC

[问题 2]:丑陋的外连接是怎么回事?

Lets say we have 3 tables, Users, Products, Purchases.
There is a view that needs to display the purchases made by a user.

I could lookup the data required by doing:

from p in DBSet<Purchases>.Include("User").Include("Product") select p;

However, I am concern that this may have a performance impact because it will retrieve the full objects.
Alternatively, I could select only the fields i need:

from p in DBSet<Purchases>.Include("User").Include("Product") select new SimplePurchaseInfo() { UserName = p.User.name, Userid = p.User.Id, ProductName = p.Product.Name ... etc };

So my question is:
Whats the best practice in doing this?

== EDIT

Thanks for all the replies.

[QUESTION 1]: I want to know whether all views should work with flat ViewModels with very specific data for that view, or should the ViewModels contain the entity objects.

Real example: User reviews Products

var query = from dr in productRepository.FindAllReviews()
            where dr.User.UserId = 'userid'
            select dr;
string sql = ((ObjectQuery)query).ToTraceString();

SELECT [Extent1].[ProductId] AS [ProductId], 
       [Extent1].[Comment] AS [Comment], 
       [Extent1].[CreatedTime] AS [CreatedTime], 
       [Extent1].[Id] AS [Id], 
       [Extent1].[Rating] AS [Rating], 
       [Extent1].[UserId] AS [UserId], 
       [Extent3].[CreatedTime] AS [CreatedTime1], 
       [Extent3].[CreatorId] AS [CreatorId], 
       [Extent3].[Description] AS [Description], 
       [Extent3].[Id] AS [Id1], 
       [Extent3].[Name] AS [Name], 
       [Extent3].[Price] AS [Price], 
       [Extent3].[Rating] AS [Rating1], 
       [Extent3].[ShopId] AS [ShopId], 
       [Extent3].[Thumbnail] AS [Thumbnail], 
       [Extent3].[Creator_UserId] AS [Creator_UserId], 
       [Extent4].[Comment] AS [Comment1], 
       [Extent4].[DateCreated] AS [DateCreated], 
       [Extent4].[DateLastActivity] AS [DateLastActivity], 
       [Extent4].[DateLastLogin] AS [DateLastLogin], 
       [Extent4].[DateLastPasswordChange] AS [DateLastPasswordChange], 
       [Extent4].[Email] AS [Email], 
       [Extent4].[Enabled] AS [Enabled], 
       [Extent4].[PasswordHash] AS [PasswordHash], 
       [Extent4].[PasswordSalt] AS [PasswordSalt], 
       [Extent4].[ScreenName] AS [ScreenName], 
       [Extent4].[Thumbnail] AS [Thumbnail1], 
       [Extent4].[UserId] AS [UserId1], 
       [Extent4].[UserName] AS [UserName]
       FROM    [ProductReviews] AS [Extent1]
       INNER JOIN [Users] AS [Extent2] ON [Extent1].[UserId] = [Extent2].[UserId]
       LEFT OUTER JOIN [Products] AS [Extent3] ON [Extent1].[ProductId] = [Extent3].[Id]
       LEFT OUTER JOIN [Users] AS [Extent4] ON [Extent1].[UserId] = [Extent4].[UserId]
       WHERE N'615005822' = [Extent2].[UserId]

or

from d in productRepository.FindAllProducts()
from dr in d.ProductReviews
where dr.User.UserId == 'userid'
orderby dr.CreatedTime
select new ProductReviewInfo()
       {
           product = new SimpleProductInfo() { Id = d.Id, Name = d.Name, Thumbnail = d.Thumbnail, Rating = d.Rating },
           Rating = dr.Rating,
           Comment = dr.Comment,
           UserId = dr.UserId,
           UserScreenName = dr.User.ScreenName,
           UserThumbnail = dr.User.Thumbnail,
           CreateTime = dr.CreatedTime
       };

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
[Extent1].[Thumbnail] AS [Thumbnail], 
[Extent1].[Rating] AS [Rating], 
[Extent2].[Rating] AS [Rating1], 
[Extent2].[Comment] AS [Comment], 
[Extent2].[UserId] AS [UserId], 
[Extent4].[ScreenName] AS [ScreenName], 
[Extent4].[Thumbnail] AS [Thumbnail1], 
[Extent2].[CreatedTime] AS [CreatedTime]
FROM    [Products] AS [Extent1]
INNER JOIN [ProductReviews] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ProductId]
INNER JOIN [Users] AS [Extent3] ON [Extent2].[UserId] = [Extent3].[UserId]
LEFT OUTER JOIN [Users] AS [Extent4] ON [Extent2].[UserId] = [Extent4].[UserId]
WHERE N'userid' = [Extent3].[UserId]
ORDER BY [Extent2].[CreatedTime] ASC

[QUESTION 2]: Whats with the ugly outer joins?

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

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

发布评论

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

评论(4

心的位置 2024-10-18 17:42:13

一般来说,只检索你需要的内容,但请记住检索足够的信息,这样你的应用程序就不会太啰嗦,所以如果你可以将一堆东西批处理在一起,那就这样做,否则你每次需要时都会付出网络流量成本返回数据库并检索更多内容。

在这种情况下,假设您只需要这些信息,我会采用第二种方法(如果这是您真正需要的)。

In general, only retrieve what you need, but keep in mind to retrieve enough information so your application is not too chatty, so if you can batch a bunch of things together, do so, otherwise you'll pay network traffic cost everytime you need to go back to the database and retrieve some more stuffs.

In this case, assuming you will only need those info, I would go with the second approach (if that's what you really need).

拥抱没勇气 2024-10-18 17:42:13

当您想要过滤(或排序)时,使用 .Include 进行预加载并不能很好地发挥作用。

第一个查询基本上是这样的:

select p.*, u.*, p2.*
from products p
left outer join users u on p.userid = u.userid
left outer join purchases p2 on p.productid = p2.productid
where u.userid == @p1

这真的是你想要的吗?

有一个视图需要显示用户进行的购买。

那么为什么要包括“产品”呢?

难道不应该是:

from p in DBSet<Purchases>.Include("User") select p;

您的第二个查询将出错。您必须投影到模型上的实体或匿名类型 - 而不是随机类/DTO。

老实说,在您当前的场景中,最简单、性能最好的选项是对 FK 本身进行查询:

var purchasesForUser = DBSet<Purchases>.Where(x => x.UserId == userId);

这应该产生:

select p.*
from products p
where p.UserId == @p1

上述查询当然要求您在模型中包含外键。

如果您的模型中没有 FK,那么您将需要更多匿名类型投影形式的 LINQ-Entities 技巧。

总的来说,不要出去寻求优化。创建符合场景/业务需求的查询,然后在必要时进行优化 - 或者寻找 LINQ 实体的替代方案,例如存储过程、视图或编译查询。

请记住:过早的优化是万恶之源。

*编辑 - 回应问题更新*

[问题 1]:我想知道是否所有视图都应该与具有该视图的非常具体数据的平面 ViewModel 一起使用,或者 ViewModel 是否应该包含实体对象。

是的 - ViewModel 应该只包含该视图所需的内容。否则为什么要有 ViewModel?您也可以直接绑定到 EF 模型。因此,仅设置视图所需的字段的 ViewModel。

[问题 2]:丑陋的外连接是怎么回事?

这是 .Include 的默认行为。 .Include 始终 生成左外连接。

Eager loading with .Include doesn't really play nice when you want filtering (or ordering for that matter).

That first query is basically this:

select p.*, u.*, p2.*
from products p
left outer join users u on p.userid = u.userid
left outer join purchases p2 on p.productid = p2.productid
where u.userid == @p1

Is that really what you want?

There is a view that needs to display the purchases made by a user.

Well then why are you including "Product"?

Shouldn't it just be:

from p in DBSet<Purchases>.Include("User") select p;

Your second query will error. You must project to an entity on the model, or an anonymous type - not a random class/DTO.

To be honest, the easiest and most well performing option in your current scenario is to query on the FK itself:

var purchasesForUser = DBSet<Purchases>.Where(x => x.UserId == userId);

That should produce:

select p.*
from products p
where p.UserId == @p1

The above query of course requires you to include the foreign keys in the model.

If you don't have the FK's in your model, then you'll need more LINQ-Entities trickery in the form of anonymous type projection.

Overall, don't go out looking to optimize. Create queries which align with the scenario/business requirement, then optimize if necessary - or look for alternatives to LINQ-Entities, such as stored procedures, views or compiled queries.

Remember: premature optimization is the root of all evil.

*EDIT - In response to Question Update *

[QUESTION 1]: I want to know whether all views should work with flat ViewModels with very specific data for that view, or should the ViewModels contain the entity objects.

Yes - ViewModel's should only contain what is required for that View. Otherwise why have the ViewModel? You may as well bind straight to the EF model. So, setup the ViewModel which only the fields it needs for the view.

[QUESTION 2]: What's with the ugly outer joins?

That is default behaviour for .Include. .Include always produces a left outer join.

淡忘如思 2024-10-18 17:42:13

我认为第二个查询将引发异常,因为您无法将结果映射到 Linq-to-entities 中未映射的 .NET 类型。您必须返回匿名类型并将其映射到 Linq-to-objects 中的对象,或者必须使用一些高级概念进行投影 - QueryView(ESQL 中的投影)或 DefiningQuery(映射到新只读实体的自定义 SQL 查询)。

一般来说,更多的是关于实体的设计。如果您选择单个小实体,则加载全部而不是投影并没有太大区别。如果您正在选择实体列表,则应考虑投影 - 特别是如果表包含结果中不需要的 nvarchar(max) 或 varbinar(max) 等列!

I think the second query will throw exception because you can't map result to unmapped .NET type in Linq-to-entities. You have to return annonymous type and map it to your object in Linq-to-objects or you have to use some advanced concepts for projections - QueryView (projections in ESQL) or DefiningQuery (custom SQL query mapped to new readonly entity).

Generally it is more about design of your entities. If you select single small entity it is not a big difference to load it all instead of projection. If you are selecting list of entities you should consider projections - expecially if tables contains columns like nvarchar(max) or varbinar(max) which are not needed in your result!

安人多梦 2024-10-18 17:42:13

两者都创建几乎相同的查询:从一个表中选择,并具有两个内部联接。从数据库的角度来看,唯一改变的是返回的字段数量,但这实际上并不重要。

我认为这里 DRY 因性能下降而获胜(如果它甚至存在的话):所以我的建议是选择第一个选项。

Both create almost the same query: select from one table, with two inner joins. The only thing that changes from a database perspective is the amount of fields returned, but that shouldn't really matter that much.

I think here DRY wins from a performance hit (if it even exists): so my call is go for the first option.

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