1

我在 SQL Server 中有一个ProductsImages表,以及一个多对多ProductImages链接表,并有一个针对此结构生成的实体框架(VS2012、.NET4.5)模型。

我有一个带有方法的 WCF 服务GetImageList,它列出了与产品关联的图像。为了速度,我只想从Images表中返回几列,特别是不包括ImageBinaryImageThumbnailBinary列,因为我们在其中存储高分辨率图像时可能会非常大。

为了证明我的查询要点,我决定只尝试获取文件名为fred.jpg.

首先,我从获取Product和使用导航属性开始:

Product p = ctx.Products.SingleOrDefault(x => x.Code == productCode);

if (p != null)
{
    var images = p.Images.Where(x => (x.FileName == "fred.jpg") && (!imageTypeId.HasValue || x.ImageTypeId == imageTypeId))
        .Select(x =>
            new
            {
                x.ID,
                x.FileName,
                x.MaxAvailableHeight,
                x.MaxAvailableWidth,
                ImageTypeName = x.ImageType.Name,
                x.FileDescription,
                HasCMYK = (x.CMYKImage != null)
            }
        ).ToList();
}

我惊讶地发现这个查询仍然很慢,尽管添加了 where 子句并且只选择了我想要的列。当我运行 SQL Profiler 时,我发现该查询转换为获取产品的每个图像的每一列,然后在内存中执行过滤和选择。这是关联的 SQL Server 跟踪,执行时间为 3541 毫秒(请注意缺少对 fred.jpg 的过滤并带回每一列):

exec sp_executesql N'SELECT 
[Extent2].[ID] AS [ID], 
[Extent2].[MimeType] AS [MimeType], 
[Extent2].[ImageTypeId] AS [ImageTypeId], 
[Extent2].[ImageBinary] AS [ImageBinary], 
[Extent2].[ImageThumbnailBinary] AS [ImageThumbnailBinary], 
[Extent2].[FileSizeKb] AS [FileSizeKb], 
[Extent2].[FileName] AS [FileName], 
[Extent2].[FileDescription] AS [FileDescription], 
[Extent2].[MaxAvailableHeight] AS [MaxAvailableHeight], 
[Extent2].[MaxAvailableWidth] AS [MaxAvailableWidth], 
[Extent2].[CMYKImage] AS [CMYKImage], 
[Extent2].[SageStockItemID] AS [SageStockItemID]
FROM  [product].[ProductImages] AS [Extent1]
INNER JOIN [product].[Images] AS [Extent2] ON [Extent1].[ImageID] = [Extent2].[ID]
WHERE [Extent1].[ProductID] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=125

然后我决定不从 theProduct和 using开始,而是.Images.Where直接从上下文开始,并为我感兴趣的产品 ID 添加一个 where 子句,如下所示:

var images = ctx.Images.Where(x => (x.Products.Any(y => y.ID == 125)) && (x.FileName == "fred.jpg") && (!imageTypeId.HasValue || x.ImageTypeId == imageTypeId))
    .Select(x =>
        new
        {
            x.ID,
            x.FileName,
            x.MaxAvailableHeight,
            x.MaxAvailableWidth,
            ImageTypeName = x.ImageType.Name,
            x.FileDescription,
            HasCMYK = (x.CMYKImage != null)
        }
    ).ToList();

令我惊讶的是,这完全符合我的要求。SQL 翻译如下,其中仅包含我想要的列和我想要的过滤器:

exec sp_executesql N'SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[FileName] AS [FileName], 
[Extent1].[MaxAvailableHeight] AS [MaxAvailableHeight], 
[Extent1].[MaxAvailableWidth] AS [MaxAvailableWidth], 
[Extent2].[Name] AS [Name], 
[Extent1].[FileDescription] AS [FileDescription], 
CASE WHEN ([Extent1].[CMYKImage] IS NOT NULL) THEN cast(1 as bit) WHEN ([Extent1].[CMYKImage] IS NULL) THEN cast(0 as bit) END AS [C1]
FROM  [product].[Images] AS [Extent1]
INNER JOIN [dbo].[ImageTypes] AS [Extent2] ON [Extent1].[ImageTypeId] = [Extent2].[ID]
WHERE ( EXISTS (SELECT 
    1 AS [C1]
    FROM [product].[ProductImages] AS [Extent3]
    WHERE ([Extent1].[ID] = [Extent3].[ImageID]) AND (125 = [Extent3].[ProductID])
)) AND (''fred.jpg'' = [Extent1].[FileName]) AND (@p__linq__0 IS NULL OR [Extent1].[ImageTypeId] = @p__linq__1)',N'@p__linq__0 int,@p__linq__1 int',@p__linq__0=1,@p__linq__1=1

根据 SQL Server 探查器,此查询执行需要 0 毫秒 - 即时!

那么,这里发生了什么,为什么 - 当我从产品开始并转到.Images它时会加载所有内容,但是如果我从实体数据上下文开始并.Images使用产品 ID 的附加过滤器,它会完美运行吗?

谢谢!

4

1 回答 1

0

The difference is that in the first example you first load a Product. Then, as far as Entity Framework is concerned, you do

p.Images

everything that happens around that call is of no interest to EF. That is because EF always loads complete entity collections and materializes entities in a complete state. It won't load the Images collection filtered by the Where phrase. And it won't load Images that only have the properties in the Select phrase.

In the second example not a single entity is loaded by EF, because you only fetch a projection and no complete entities (no Product and no Image). If you're in the DbContext API, you can verify that by checking context.Products.Local.Count.

There is a way to do a projection from an entity's collection. Following your example that would be something like:

context.Entry(p).Collection(x => x.Images).Query()
       .Select(x => new
                    {
                        x.ID,
                        x.FileName,
                        ....
于 2013-05-08T14:48:44.427 回答