4

データ層に EF 4.3 を使用しており、一般的なリポジトリ パターンが用意されています。バックエンドは SQL 2008 R2 で、プロジェクトは .NET 4.0/MVC 3 ですが、これが問題の要因になるとは思いません。

基本的に、2 つのオブジェクトのデータベースには 1 対多の関係があります。1 つは「トラップ」用で、2 つ目は「トラップ アクティビティ」用です。つまり、これらの「トラップ」の 1 つが展開されると、そのトラップに発生したすべてのことがトラップ アクティビティ テーブルに保持されます。これを行うためのかなり簡単な方法である必要があります。

この関係は、「トラップ アクティビティ」テーブルの 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 プロパティに関するものです。書かれているように、これは約 3000 項目のリストを返すのに 25 秒かかります。トラップ テーブルを更新して展開日を格納し、次の行を入力します。

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 のリストを取得し、それぞれに対してクエリを実行しているように見えます。その結果、何千もの SQL ステートメントが生成されます。また、郡情報についても個別のクエリを実行しているようです。

4

2 に答える 2

1

リポジトリではObjectQuery.ToTraceString、オブジェクトを返す前にどの SQL が実行されているかを確認するために使用できます。

TrapIQueryable の代わりにリポジトリの find メソッドから 2012 年にデプロイされた実際のオブジェクトをすべて返していますTrapActivities。つまりSelect、ビュー モデルを作成するために結果を列挙するときに、新しいクエリをそれぞれの DB に送信して、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;

これにより、1 つのクエリですべてのTrapActivitiesトラップがロードされます。ただし、あなたの場合、最初の展開アクティビティのみが必要なため、TrapFirstDeploymentクラスを作成しました。このようにして、EF は最初の展開アクティビティのみを取得する必要があります。

更新 2

署名に一致するようFindに、リポジトリのメソッドのパラメーターも変更する必要があります。が呼び出される前に変換されるのはそのためです。Expression<Func<T,Boolean>>IQueryable.WhereIEnumerable.WhereFunc<T,Boolean>objectSetIEnumberableWhere

于 2012-04-30T14:28:40.460 に答える
1

@SLC が言ったように: EF が生成している SQL を見る必要があります - あなたは驚かれることでしょう。

LINQPadの使用をお勧めします。無料版と有料版があります。

私が最も気に入っているのは、データ層アセンブリをインポートして、モデルに対して LINQ ステートメントを記述できることです。これにより、さまざまなクエリ アプローチを簡単にテストできます。


修正は、の代わりにIQueryableから戻るのと同じくらい簡単です。FindIEnumerable

于 2012-04-30T14:19:03.623 に答える