4

我们将 EF 4.3 用于我们的数据层,并有一个通用的存储库模式。后端是 SQL 2008 R2,项目是 .NET 4.0/MVC 3,但我认为这不是问题的因素。

基本上,我们在两个对象的数据库中有一对多的关系。一个用于“陷阱”,第二个用于“陷阱活动”。这意味着,一旦部署了这些“陷阱”之一,该陷阱发生的任何事情都会保存在“陷阱活动”表中。应该是一种相当简单的方法。

这种关系是用“Trap Activity”表中的 FK 与“Traps”表的 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 个项目的列表。更新 Trap 表以将部署日期存储在那里并使用以下行填充:

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'

在我看来,它正在获取一个陷阱 ID 列表,然后对每个陷阱 ID 运行一个查询,从而生成数千条 SQL 语句。它似乎也在对县信息进行单独查询。

4

2 回答 2

1

正如@SLC 所说:您需要查看 EF 生成的 SQL - 您会感到惊讶。

我建议使用LINQPad。有免费和付费版本。

我最喜欢的是您可以导入数据层程序集并针对您的模型编写 LINQ 语句。它使测试不同的查询方法变得容易。


修复可以像IQueryable从而Find不是返回一样简单IEnumerable

于 2012-04-30T14:19:03.623 回答
1

在您的存储库中,您可以ObjectQuery.ToTraceString在返回对象之前查看正在执行的 SQL。

Trap将从存储库 find 方法返回 2012 年部署的所有实际对象,而不是 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的存储库一起加载,因此它会等到您第一次访问它时才加载它们。这很好,您需要陷阱而不是活动,因为它减少了进出数据库的流量。但是,在某些情况下,您需要它们。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要转换为IEnumberablebeforeWhere被调用的原因。

于 2012-04-30T14:28:40.460 回答