我们正在为我们的数据层使用EF 4.3,并且具有通用的存储库模式。后端是SQL 2008 R2,项目是.NET 4.0 / MVC 3,但我不认为这会影响问题。
基本上,我们在两个对象的数据库中有一对多的关系。一个是“陷阱”,第二个是“陷阱活动”。这意味着,一旦部署了这些“陷阱”之一,发生在该陷阱中的任何内容都会保留在“陷阱活动”表中。这应该是一种相当直接的方式。
关系是使用“陷阱活动”表中的FK定义为“陷阱”表的PK。两个表都定义了PK。
在我们的服务层中,我需要查询一个“陷阱”列表,其中列出了这些陷阱的部署日期。这是通过以下代码片段完成的:
var traps = this.trapRepository.Find(x => x.DeploymentYear == 2012).Select(x => new TrapHomeViewModel
{
County = x.County.Name,
DeploymentDate = x.TrapActivities.First(y => y.ActivityType == 1).ActivityDate,
State = x.County.CountyState.Abbreviation,
Latitude = x.Latitude,
Longitude = x.Longitude,
TrapId = x.TrapID,
TrapNumber = x.SerialNumber,
Centroid = x.TrapCentroid
}).ToList();
问题在于DeploymentDate属性。如上所述,这需要25秒才能返回大约3000个项目的列表。更新陷阱表以使部署日期存储在那里并填充此行:
DeploymentDate = x.DeploymentDate.Value.Date
响应时间不到1秒。现在我想我知道这里发生了什么(数据集的多个枚举),但我认为会发生类似于以下的查询:
SELECT Counties.Name, TrapActivities.ActivityDate, States.Abbreviation,
Traps.Latitude, Traps.Longitude, Traps.TrapID, Traps.SerialNumber, Traps.TrapCentroid
FROM TrapActivities INNER JOIN
Traps ON TrapActivities.TrapID = Traps.TrapID INNER JOIN
Counties ON Traps.CountyID = Counties.CountyID INNER JOIN
States ON Counties.State = States.FIPS_Code
WHERE (TrapActivities.ActivityType = 1)
......但似乎并非如此。有了上面的所有背景信息,我在哪里填写这个视图模型?我不认为我之前遇到过这个问题,但这也是一个比我们其他项目更大的数据集。任何有关这方面的指导都会有所帮助。如果我需要提供任何其他信息,请告诉我。
修改
根据要求,GenericRepository Find方法和构造函数:
public class GenericRepository<T> : IGenericRepository<T>
where T : class
{
private readonly IObjectSet<T> objectSet;
private ObjectContext context;
public GenericRepository()
: this(new APHISEntities())
{
}
public GenericRepository(ObjectContext context)
{
this.context = context;
this.objectSet = this.context.CreateObjectSet<T>();
}
public IEnumerable<T> Find(Func<T, bool> predicate)
{
return this.objectSet.Where(predicate);
}
编辑2
这是上面代码生成的SQL示例:
exec sp_executesql N'SELECT
[Extent1].[TrapActivityID] AS [TrapActivityID],
[Extent1].[TrapID] AS [TrapID],
[Extent1].[ActivityType] AS [ActivityType],
[Extent1].[Notes] AS [Notes],
[Extent1].[AgentID] AS [AgentID],
[Extent1].[ActivityDate] AS [ActivityDate],
[Extent1].[CreatedOn] AS [CreatedOn],
[Extent1].[EditedOn] AS [EditedOn],
[Extent1].[Deleted] AS [Deleted],
[Extent1].[VisualInspectionID] AS [VisualInspectionID]
FROM [dbo].[TrapActivities] AS [Extent1]
WHERE [Extent1].[TrapID] = @EntityKeyValue1',N'@EntityKeyValue1 uniqueidentifier',@EntityKeyValue1='FEBC7ED4-E726-4F5E-B2BA-FFD53AB7DF34'
在我看来,它正在获取Trap ID列表,然后为每个ID运行查询,从而生成数千个SQL语句。它似乎也在为县信息运行个人查询。
答案 0 :(得分:1)
正如@SLC所说:你需要看看EF正在生成的SQL - 你会感到惊讶。
我建议使用LINQPad。有免费和付费版本。
我最喜欢的是您可以导入数据层程序集并针对您的模型编写LINQ语句。它可以轻松测试不同的查询方法。
此修复程序可以像从IQueryable
而不是Find
返回IEnumerable
一样简单。
答案 1 :(得分:1)
在您的存储库中,您可以使用ObjectQuery.ToTraceString
查看在返回对象之前执行的SQL。
您将从存储库查找方法返回2012年部署的所有实际Trap
对象,而不是IQueryable并且您不急于加载TrapActivities
。这意味着,当您通过Select
枚举结果来创建视图模型时,您将向每个Trap
的数据库发送一个新查询,以获取它TrapActivities
。
更新1
我认为您需要在存储库中为此实现特定查询。
var q = from t in traps
where t.DeploymentYear == 2012
select new TrapFirstDeployment {
Trap = t,
DeploymentActivity = t.TrapActivities.Where(ta=>ta.FirstOrDefault(a=>a.ActivityType=1))
};
return q.Where(tfd=>tfd.DeploymentActivity != null);
<强>解释强>
您的初始查询速度慢的原因是因为除非您告诉它,否则EF不会急于加载子关系。默认情况下启用延迟加载。由于您没有告诉它在您的存储库中加载TrapActivities
Trap
,因此它会等到您第一次加载它时才会访问它。这很好,你需要陷阱而不是活动,因为它减少了进出DB的流量。但是,在某些情况下,您需要它们。在这种情况下,您可以通过在查询中添加Include
来强制加急。例如,
var q = from t in this.objectSet.Include('TrapActivities')
select t;
这会在一个查询中加载带有陷阱的所有TrapActivities
。但是,在您的情况下,您只需要第一次部署活动,这就是我创建TrapFirstDeployment
类的原因。这样,EF应该只抓取第一个部署活动。
更新2
您还应该将存储库中Find
方法的参数更改为Expression<Func<T,Boolean>>
以匹配IQueryable.Where
签名。 IEnumerable.Where
使用Func<T,Boolean>
,这就是objectSet
在调用IEnumberable
之前转换为Where
的原因。