我在SQL Server中有一个Products
和Images
表,以及一个多对多ProductImages
链接表,并且有一个实体框架(VS2012,.NET4.5)针对这种结构生成的模型。
我有一个带有方法GetImageList
的WCF服务,该方法列出了与产品关联的图像。对于速度,我只想从Images
表中返回几列,特别是排除ImageBinary
和ImageThumbnailBinary
列,这些列可能非常大,因为我们在那里存储了高分辨率图像
为了证明我的疑问,我决定只尝试获取文件名为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事件探查器时,我发现此查询转换为获取产品的每个映像的每个列,然后执行过滤器并选择内存中。这是相关的SQL Server跟踪,执行时需要3541ms(注意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
然后我决定不是从Product
开始并使用.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分析器执行0ms执行 - 即时!
所以,这里发生了什么,为什么 - 当我开始使用该产品并转到.Images
它是否会加载所有内容,但如果我从实体数据上下文开始并转到.Images
产品ID的附加过滤器,它运作得很好吗?
谢谢!
答案 0 :(得分:0)
不同之处在于,在第一个示例中,您首先加载Product
。然后,就实体框架而言,你做了
p.Images
围绕该呼叫发生的一切都与EF无关。这是因为EF总是加载完整的实体集合和实现完整状态的实体。它不会加载由Images
短语过滤的Where
集合。并且它不会加载只有Image
短语中的属性的Select
。
在第二个示例中,EF不会加载单个实体,因为您只获取投影而没有完整的实体(没有Product
而没有Image
)。如果您使用的是DbContext API,则可以通过选中context.Products.Local.Count
。
有一种方法可以从实体的集合中进行投影。按照你的例子:
context.Entry(p).Collection(x => x.Images).Query()
.Select(x => new
{
x.ID,
x.FileName,
....