Entity Frameworkを使用する際の優れた設計方法は何ですか

asp.net entity-framework visual-studio-2008

質問

これは、データがsoa経由でアクセスされないasp.netアプリケーションにほとんど当てはまります。転送オブジェクトではなく、フレームワークからロードされたオブジェクトにアクセスできることを意味しますが、それでもいくつかの推奨事項が適用されます。

これはコミュニティへの投稿ですので、是非追加してください。

適用対象 :Visual Studio 2008 SP1に同梱されているEntity Framework 1.0。

そもそもなぜEFを選ぶのですか?

それが多くの問題を抱えている若い技術であることを考えると(下記参照)、あなたのプロジェクトのためにEFの流行に乗るのは難しい売りかもしれません。しかし、それはMicrosoftが推進している技術です(EFのサブセットであるLinq2Sqlを犠牲にして)。さらに、NHibernateやその他の解決策に満足していないかもしれません。その理由がどうであれ、そこには(私も含めて)EFと仕事をする人々がいますし、人生は悪くありません。

EFと継承

最初の大きな課題は継承です。 EFは、クラスごとのテーブルと階層のテーブルという2つの方法で永続化される継承クラスのマッピングをサポートしています。モデリングは簡単で、その部分に関するプログラミングの問題はありません。

実際の問題は、クラスごとのテーブルの一部である1つまたは複数のオブジェクトを含むクエリを実行しようとしているときに発生します。継承ツリー:生成されたSQLは非常にひどいもので、EFによって解析されるのに長い時間がかかり、実行にも長い時間がかかります。これは本当のショーストッパーです。 EFは、継承やできる限り少なくして使用するべきではありません。

これがどれほど悪かったかの例です。私のEFモデルは30クラスまであり、そのうちの10クラスは継承ツリーの一部です。 Baseクラスから1つの項目を取得するためのクエリを実行すると、Base.Get(id)のような単純なもので、生成されたSQLは50,000文字を超えていました。その後、いくつかのアソシエーションを返そうとすると、一度に256を超えるテーブルを照会できないというSQL例外が発生するまで、さらに多くの縮退が起こります。

さて、これは悪いことです。EFの概念は、あなたのテーブルの実際のデータベース実装を考慮することなく(あるいはできるだけ最小限にして)あなたのオブジェクト構造を作成できるようにすることです。これで完全に失敗します。

それで、推奨?可能であれば継承を避けてください。パフォーマンスはずっと良くなります。あなたがしなければならないところでそれを控えめに使ってください。私の意見では、これはEFを問い合わせのための栄光のSQL生成ツールにしますが、それを使用することにはまだ利点があります。継承に似たメカニズムを実装する方法。

インタフェースで継承を回避する

ある種の継承をEFで行おうとしたときに知っておくべき最初のことは、EFでモデル化されていないクラスに基本クラスを割り当てることはできないということです。試してもいけない、それはモデラーによって上書きされます。じゃあ何をすればいいの?

インターフェースを使用して、クラスにいくつかの機能を実装させることができます。たとえば、設計時にエンティティの種類がわからない場合に、EFエンティティ間の関連付けを定義できるようにするIEntityインターフェイスがあります。

public enum EntityTypes{ Unknown = -1, Dog = 0, Cat }
public interface IEntity
{
    int EntityID { get; }
    string Name { get; }
    Type EntityType { get; }
}
public partial class Dog : IEntity
{
   // implement EntityID and Name which could actually be fields 
   // from your EF model
   Type EntityType{ get{ return EntityTypes.Dog; } }
}

このIEntityを使用して、他のクラスの未定義の関連付けを操作できます。

// lets take a class that you defined in your model.
// that class has a mapping to the columns: PetID, PetType
public partial class Person
{
    public IEntity GetPet()
    {
        return IEntityController.Get(PetID,PetType);
    }
}

これはいくつかの拡張機能を利用します。

public class IEntityController
{
    static public IEntity Get(int id, EntityTypes type)
    {
        switch (type)
        {
            case EntityTypes.Dog: return Dog.Get(id);
            case EntityTypes.Cat: return Cat.Get(id);
            default: throw new Exception("Invalid EntityType");
        }
    }
}

特にPetTypeを追加のデータベースフィールドに格納する必要があることを考えると、単純な継承があるほどきれいではありませんが、パフォーマンスの向上を考えると、私は振り返りません。

また、1対多、多対多の関係をモデル化することはできませんが、 'Union'をクリエイティブに使用することで機能させることができます。最後に、それはあなたが注意する必要があるオブジェクトのプロパティ/関数にデータをロードするという副次的な効果を生み出します。その点では、GetXYZ()のような明確な命名規則を使用すると便利です。

コンパイル済みクエリ

Entity Frameworkのパフォーマンスは、(明らかに)ADOやLinq2SQLを使った直接データベースアクセスほどは良くありません。改善する方法はいくつかありますが、そのうちの1つはクエリをコンパイルすることです。コンパイルされたクエリのパフォーマンスはLinq2Sqlに似ています。

コンパイルされたクエリとは何ですか?これは、解析済みのツリーをメモリ内に保持するようにフレームワークに指示するための単純なクエリです。したがって、次回実行したときに再生成する必要はありません。次回の実行では、ツリーの解析にかかる時間を節約します。複雑なクエリではさらに悪化する非常にコストのかかる操作なので、それを割り引かないでください。

クエリをコンパイルする方法は2つあります。EntitySQLでObjectQueryを作成する方法とCompiledQuery.Compile()関数を使用する方法です。 (ページ内でEntityDataSourceを使用することで、実際にはEntitySQLと共にObjectQueryを使用することになるので、コンパイルおよびキャッシュされます)。

EntitySQLが何であるかがわからない場合のために、ここで取っておきます。 EFに対するクエリを記述する文字列ベースの方法です。例は次のとおりです。 "Entities.DogSetからdogとして値dogを選択します。ここで、dog.ID = @ID"です。構文はSQL構文とよく似ています。かなり複雑なオブジェクト操作もできます。これについてはよく説明されています[1]。

それでは、ObjectQuery <>を使用してそれを行う方法は次のとおりです。

        string query = "select value dog " +
                       "from Entities.DogSet as dog " +
                       "where dog.ID = @ID";

        ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance));
        oQuery.Parameters.Add(new ObjectParameter("ID", id));
        oQuery.EnablePlanCaching = true;
        return oQuery.FirstOrDefault();

このクエリを初めて実行したときに、フレームワークは式ツリーを生成し、それをメモリ内に保持します。そのため、次回実行されるときは、コストのかかる手順を省くことができます。この例では、EnablePlanCaching = trueです。これはデフォルトのオプションであるため、不要です。

後で使用するためにクエリをコンパイルするもう1つの方法は、CompiledQuery.Compileメソッドです。これはデリゲートを使います。

    static readonly Func<Entities, int, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
            ctx.DogSet.FirstOrDefault(it => it.ID == id));

またはlinqを使う

    static readonly Func<Entities, int, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
            (from dog in ctx.DogSet where dog.ID == id select dog).FirstOrDefault());

クエリを呼び出すには:

query_GetDog.Invoke( YourContext, id );

CompiledQueryの利点は、EntitySQLではなく、コンパイル時にクエリの構文がチェックされることです。しかし、他の考慮事項があります...

含む

データベースへの2回の呼び出しを回避するために、犬の飼い主のデータをクエリで返すようにしたいとしましょう。簡単ですね。

EntitySQL

        string query = "select value dog " +
                       "from Entities.DogSet as dog " +
                       "where dog.ID = @ID";
        ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)).Include("Owner");
        oQuery.Parameters.Add(new ObjectParameter("ID", id));
        oQuery.EnablePlanCaching = true;
        return oQuery.FirstOrDefault();

CompiledQuery

    static readonly Func<Entities, int, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
            (from dog in ctx.DogSet.Include("Owner") where dog.ID == id select dog).FirstOrDefault());

では、Includeをパラメータ化したい場合はどうしますか。私が言っているのは、犬のさまざまな関係を気にするさまざまなページから呼び出される単一のGet()関数が必要だということです。 1つはオーナー、もう1つはFavoriteFood、もう1つはFavotireToyなどに関心があります。基本的には、どのアソシエーションをロードするのかをクエリに伝えます。

EntitySQLを使うのは簡単です

public Dog Get(int id, string include)
{
        string query = "select value dog " +
                       "from Entities.DogSet as dog " +
                       "where dog.ID = @ID";

        ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance))
    .IncludeMany(include);
        oQuery.Parameters.Add(new ObjectParameter("ID", id));
        oQuery.EnablePlanCaching = true;
        return oQuery.FirstOrDefault();
}

インクルードは単に渡された文字列を使用します。とても簡単です。コンマで区切られた関連付けの文字列をロードに渡すことができるIncludeMany(文字列)を使用して(単一のパスのみを受け入れる)Include(文字列)関数を改良することが可能です。この機能については、拡張機能のセクションで詳しく調べてください。

しかし、CompiledQueryでそれをやろうとすると、数多くの問題が発生します。

明らかな

    static readonly Func<Entities, int, string, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) =>
            (from dog in ctx.DogSet.Include(include) where dog.ID == id select dog).FirstOrDefault());

と呼ばれたときに窒息します:

query_GetDog.Invoke( YourContext, id, "Owner,FavoriteFood" );

なぜなら、上記で述べたように、Include()は文字列内の単一のパスを見たいだけなので、ここでは2: "Owner"と "FavoriteFood"(これを "Owner.FavoriteFood"と混同しないでください)!

それでは、拡張機能であるIncludeMany()を使用しましょう。

    static readonly Func<Entities, int, string, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) =>
            (from dog in ctx.DogSet.IncludeMany(include) where dog.ID == id select dog).FirstOrDefault());

またしても、今回は、EFがIncludeManyを構文解析できないためです。これは、認識される関数の一部ではないためです。これは、拡張子です。

さて、あなたはあなたの関数に任意の数のパスを渡したいのですが、Includes()は一つだけを取ります。何をすべきか?あなたはこれ以上必要としないことを決定することができます、例えば20インクルードと言い、CompiledQueryに構造体でそれぞれ分離された文字列を渡します。しかし今、クエリは次のようになります。

from dog in ctx.DogSet.Include(include1).Include(include2).Include(include3)
.Include(include4).Include(include5).Include(include6)
.[...].Include(include19).Include(include20) where dog.ID == id select dog

これもひどいです。それでは、ちょっと待ってください。 CompiledQueryでObjectQuery <>を返すことはできませんか?それからそれにインクルードを設定しますか?まあ、私もそう思っていただろうということ:

    static readonly Func<Entities, int, ObjectQuery<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, ObjectQuery<Dog>>((ctx, id) =>
            (ObjectQuery<Dog>)(from dog in ctx.DogSet where dog.ID == id select dog));
public Dog GetDog( int id, string include )
{
    ObjectQuery<Dog> oQuery = query_GetDog(id);
    oQuery = oQuery.IncludeMany(include);
    return oQuery.FirstOrDefault;   
}

ただし、IncludeMany(またはInclude、Where、OrderBy ...)を呼び出すと、キャッシュされたコンパイル済みクエリは無効になります。ただし、これはまったく新しいクエリです。そのため、式ツリーを再解析する必要があり、そのパフォーマンスが再び打撃を受けます。

それでは解決策は何ですか? CompiledQueriesを、パラメータ化されたインクルードと一緒に使用することはできません。代わりにEntitySQLを使用してください。これはCompiledQueriesの用途がないという意味ではありません。常に同じコンテキストで呼び出されるローカライズされたクエリに最適です。構文はコンパイル時にチェックされるため、理想的にはCompiledQueryを常に使用する必要がありますが、制限のため、これは不可能です。

使用例は次のようになります。2つの犬が同じ好きな食べ物を持っているかどうかを照会するページが必要な場合があります。これはBusinessLayer関数にとってはやや狭いため、ページに入れてインクルードの種類を正確に知ってください必須。

CompiledQueryに3つ以上のパラメータを渡す

Funcは5つのパラメータに制限されています。最後のものは戻り値の型で、最初のものはモデルからのEntitiesオブジェクトです。それであなたは3つのパラメータをあなたに残します。しかし、それは非常に簡単に改善することができます。

public struct MyParams
{
    public string param1;
    public int param2;
    public DateTime param3;
}

    static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((ctx, myParams) =>
            from dog in ctx.DogSet where dog.Age == myParams.param2 && dog.Name == myParams.param1 and dog.BirthDate > myParams.param3 select dog);

public List<Dog> GetSomeDogs( int age, string Name, DateTime birthDate )
{
    MyParams myParams = new MyParams();
    myParams.param1 = name;
    myParams.param2 = age;
    myParams.param3 = birthDate;
    return query_GetDog(YourContext,myParams).ToList();
}

戻り値の型(これは、実行時にCompiledQueryメソッドと同時にコンパイルされないため、EntitySQLクエリには適用されません)

他の下流の関数が何らかの方法でクエリを変更したい場合に備えて、Linqを使用して、通常は最後の瞬間までクエリの実行を強制しません。

    static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) =>
            from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog);

public IEnumerable<Dog> GetSomeDogs( int age, string name )
{
    return query_GetDog(YourContext,age,name);
}
public void DataBindStuff()
{
    IEnumerable<Dog> dogs = GetSomeDogs(4,"Bud");
    // but I want the dogs ordered by BirthDate
    gridView.DataSource = dogs.OrderBy( it => it.BirthDate );

}

ここで何が起こるのでしょうか?元のObjectQuery(つまり、IEnumerableを実装するLinqステートメントの実際の戻り型)を引き続き使用すると、コンパイルされたクエリが無効になり、強制的に再解析されます。そのため、経験則ではなく、代わりにオブジェクトのList <>を返します。

    static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) =>
            from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog);

public List<Dog> GetSomeDogs( int age, string name )
{
    return query_GetDog(YourContext,age,name).ToList(); //<== change here
}
public void DataBindStuff()
{
    List<Dog> dogs = GetSomeDogs(4,"Bud");
    // but I want the dogs ordered by BirthDate
    gridView.DataSource = dogs.OrderBy( it => it.BirthDate );

}

ToList()を呼び出すと、クエリはコンパイルされたクエリに従って実行され、その後、OrderByがメモリ内のオブジェクトに対して実行されます。少し遅くなるかもしれませんが、私にはよくわかりません。 1つ確かなことは、ObjectQueryを誤って処理し、コンパイルされたクエリプランを無効にすることについて心配する必要がないということです。

繰り返しますが、それは包括的な声明ではありません。 ToList()は防御的なプログラミングトリックですが、ToList()を使用しない正当な理由がある場合は先に進んでください。実行する前にクエリを改良したい場合がたくさんあります。

パフォーマンス

クエリをコンパイルすることによるパフォーマンスへの影響は何ですか?実際にはかなり大きくなる可能性があります。経験則では、再利用のためにクエリをコンパイルしてキャッシュすると、キャッシュせずに単純にクエリを実行する時間の2倍以上の時間がかかります。複雑な問い合わせ(既読)については、私は10秒まで見ました。

したがって、プリコンパイルされたクエリが最初に呼び出されるときには、パフォーマンスが低下します。その最初のヒットの後、パフォーマンスは同じ非プリコンパイルクエリよりも著しく優れています。 Linq2Sqlと実質的に同じ

あなたが最初にコンパイルされたクエリでページをロードするとき、あなたはヒットするでしょう。それはおそらく5〜15秒でロードされ(明らかに複数の事前にコンパイルされたクエリが呼び出されることになるでしょう)、その後のロードは300ミリ秒未満になります。劇的な違いです。最初のユーザーがヒットしても大丈夫なのか、それともスクリプトを使用してページを呼び出してクエリを強制的に編集したいのかは、あなた次第です。

このクエリはキャッシュできますか?

{
    Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog;
}

いいえ、アドホックなLinqクエリはキャッシュされないので、呼び出すたびにツリーを生成するコストがかかります。

パラメータ化クエリ

ほとんどの検索機能は、高度にパラメータ化されたクエリを含みます。あなたがlamba表現からパラメータ化されたクエリを構築することを可能にする利用可能なライブラリさえあります。問題は、事前にコンパイルされたクエリをそれらと一緒に使用できないことです。これを回避する1つの方法は、クエリ内のすべての可能な基準をマップし、どれを使用したいかというフラグを立てることです。

public struct MyParams
{
    public string name;
public bool checkName;
    public int age;
public bool checkAge;
}

    static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((ctx, myParams) =>
            from dog in ctx.DogSet 
    where (myParams.checkAge == true && dog.Age == myParams.age) 
        && (myParams.checkName == true && dog.Name == myParams.name ) 
    select dog);

protected List<Dog> GetSomeDogs()
{
    MyParams myParams = new MyParams();
    myParams.name = "Bud";
    myParams.checkName = true;
    myParams.age = 0;
    myParams.checkAge = false;
    return query_GetDog(YourContext,myParams).ToList();
}

ここでの利点は、あなたが事前にコンパイルされたquertのすべての利益を得るということです。不利な点は、ほとんどの場合、保守がかなり難しいwhere句が作成されること、クエリのプリコンパイルに大きなペナルティが課されること、および実行する各クエリが効率的ではないことです(特に結合がスローされます)。

もう1つの方法は、私たち全員がSQLで行ったように、EntitySQLクエリを1つずつ構築することです。

protected List<Dod> GetSomeDogs( string name, int age)
{
string query = "select value dog from Entities.DogSet where 1 = 1 ";
    if( !String.IsNullOrEmpty(name) )
        query = query + " and dog.Name == @Name ";
if( age > 0 )
    query = query + " and dog.Age == @Age ";

    ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>( query, YourContext );
    if( !String.IsNullOrEmpty(name) )
        oQuery.Parameters.Add( new ObjectParameter( "Name", name ) );
if( age > 0 )
        oQuery.Parameters.Add( new ObjectParameter( "Age", age ) );

return oQuery.ToList();
}

問題は次のとおりです。 - コンパイル中に構文チェックは行われません。 - パラメータの異なる組み合わせごとに異なるクエリが生成されます。最初の実行時には、それらを事前にコンパイルする必要があります。この場合、可能なクエリは4つしかありません(パラメータなし、年齢のみ、名前のみ、および両方のパラメータ)が、通常の世界検索ではもっと多くのことができることがわかります。 - 誰もが文字列を連結するのが好きです!

別の選択肢は、データの大きなサブセットを照会してから、それをメモリー内で絞り込むことです。これは、都市のすべての犬のように、データの特定のサブセットを扱う場合に特に便利です。たくさんあることは知っていますが、そんなに多くはいないことを知っています...だからあなたのCityDog検索ページはメモリ内のその都市の全ての犬をロードすることができます。

protected List<Dod> GetSomeDogs( string name, int age, string city)
{
string query = "select value dog from Entities.DogSet where dog.Owner.Address.City == @City ";
    ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>( query, YourContext );
    oQuery.Parameters.Add( new ObjectParameter( "City", city ) );

List<Dog> dogs = oQuery.ToList();

if( !String.IsNullOrEmpty(name) )
        dogs = dogs.Where( it => it.Name == name );
if( age > 0 )
        dogs = dogs.Where( it => it.Age == age );

return dogs;
}

すべてのデータの表示を開始してからフィルタリングを可能にするときに特に便利です。

問題: - あなたがあなたのサブセットについて注意していない場合、深刻なデータ転送につながる可能性があります。 - あなたはあなたが戻ってきたデータにのみフィルタをかけることができます。それはあなたがDog.Ownerアソシエーションを返さないなら、あなたはDog.Owner.Nameでフィルタリングすることができないだろうということを意味しますそれで最善の解決策は何ですか?ありません。あなたはあなたとあなたの問題に最適な解決策を選ぶ必要があります: - あなたがあなたの質問を事前にコンパイルすることを気にしないときラムダベースの質問構築を使用してください。 - オブジェクト構造が複雑でない場合は、完全に定義済みのコンパイル済みのLinqクエリを使用してください。 - 構造が複雑になる可能性があり、結果として得られるクエリの数が少ない場合(つまり、プリコンパイルヒットが少なくなる場合)、EntitySQL /文字列連結を使用します。 - 小さいデータのサブセットを扱うとき、または最初にデータ上のすべてのデータをフェッチする必要があるときは、インメモリフィルタリングを使用します(すべてのデータでパフォーマンスが良好であれば、メモリ内のフィルタリングは行われません)。任意の時間をdbに費やすようにします。

シングルトンアクセス

すべてのページにまたがってコンテキストとエンティティを処理する最良の方法は、シングルトンパターンを使用することです。

public sealed class YourContext
{
    private const string instanceKey = "On3GoModelKey";

    YourContext(){}

    public static YourEntities Instance
    {
        get
        {
            HttpContext context = HttpContext.Current;
            if( context == null )
                return Nested.instance;

            if (context.Items[instanceKey] == null)
            {
                On3GoEntities entity = new On3GoEntities();
                context.Items[instanceKey] = entity;
            }
            return (YourEntities)context.Items[instanceKey];
        }
    }

    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly YourEntities instance = new YourEntities();
    }
}

NoTracking、それは価値がありますか?

クエリを実行するときに、フレームワークが返すオブジェクトを追跡するようにフレームワークに指示することができます。どういう意味ですか?トラッキングを有効にすると(デフォルトのオプション)、フレームワークはオブジェクトで何が起こっているのかを追跡し(変更されたか、作成されたか削除されたか)、さらにデータベースからクエリが行われるとオブジェクトをリンクします。ここに興味があります。

たとえば、ID == 2の犬にID == 10の所有者がいるとします。

Dog dog = (from dog in YourContext.DogSet where dog.ID == 2 select dog).FirstOrDefault();
    //dog.OwnerReference.IsLoaded == false;
    Person owner = (from o in YourContext.PersonSet where o.ID == 10 select dog).FirstOrDefault();
    //dog.OwnerReference.IsLoaded == true;

追跡なしで同じことをした場合、結果は異なります。

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)
    (from dog in YourContext.DogSet where dog.ID == 2 select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
Dog dog = oDogQuery.FirstOrDefault();
    //dog.OwnerReference.IsLoaded == false;
ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)
    (from o in YourContext.PersonSet where o.ID == 10 select o);
oPersonQuery.MergeOption = MergeOption.NoTracking;
    Owner owner = oPersonQuery.FirstOrDefault();
    //dog.OwnerReference.IsLoaded == false;

トラッキングは非常に便利で、パフォーマンスの問題がない完璧な世界では常に有効です。しかし、この世界では、パフォーマンスの面でそれには代償があります。それで、あなたは物事をスピードアップするためにNoTrackingを使うべきですか?それはあなたがデータを使うことを計画しているものによります。

NoTrackingを使用して照会したデータを使用して、データベース内で更新/挿入/削除を行うことができる可能性はありますか?そうであれば、関連付けが追跡されず例外がスローされるのでNoTrackingを使用しないでください。

データベースへの更新がまったくないページでは、NoTrackingを使用できます。

トラッキングとNoTrackingを混在させることは可能ですが、更新/挿入/削除には細心の注意を払う必要があります。問題は、混在させると、フレームワークがNoTrackingオブジェクトを、トラッキングをオンにした状態で同じオブジェクトの別のコピーが存在するコンテキストにAttach()しようとする危険性があることです。基本的に、私が言っているのはそれです

Dog dog1 = (from dog in YourContext.DogSet where dog.ID == 2).FirstOrDefault();

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)
    (from dog in YourContext.DogSet where dog.ID == 2 select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
Dog dog2 = oDogQuery.FirstOrDefault();

dog1とdog2は2つの異なるオブジェクトです。1つは追跡され、もう1つは追跡されません。更新/挿入で切り離されたオブジェクトを使用すると、Attach()が「ちょっと待って、同じデータベースキーを持つオブジェクトがすでに存在します。失敗します」と表示されます。そして、1つのオブジェクトをAttach()すると、そのすべての階層も同様に添付され、至る所で問題が発生します。特に注意してください。

NoTrackingの方がどれくらい速いですか

クエリによって異なります。あるものは他のものよりずっと追跡に敏感です。速く簡単な規則はありませんが、役に立ちます。

それで、どこでもNoTrackingを使うべきですか?

ではない正確に。追跡対象にはいくつかの利点があります。 1つ目は、オブジェクトがキャッシュされているため、それ以降のそのオブジェクトの呼び出しではデータベースにアクセスできません。このキャッシュは、YourEntitiesオブジェクトの有効期間に対してのみ有効です。上記のシングルトンコードを使用する場合、これはページの有効期間と同じです。 1ページのリクエスト== one YourEntityオブジェクト。したがって、同じオブジェクトに対する複数の呼び出しでは、ページ要求ごとに1回だけロードされます。 (他のキャッシングメカニズムはそれを拡張することができました)。

NoTrackingを使用していて、同じオブジェクトを複数回ロードしようとするとどうなりますか?データベースは毎回照会されるため、そこに影響があります。 1ページのリクエスト中に、どのくらいの頻度で同じオブジェクトを呼び出すべきですか。可能な限り少ないですが、それは起こります。

アソシエーションを自動的に接続させることについての上記の記事も覚えていますか。 NoTrackingにはそれがないので、データを複数のバッチにロードしても、それらの間のリンクはありません。

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)(from dog in YourContext.DogSet select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
List<Dog> dogs = oDogQuery.ToList();

ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)(from o in YourContext.PersonSet  select o);
oPersonQuery.MergeOption = MergeOption.NoTracking;
    List<Person> owners = oPersonQuery.ToList();

この場合、どの犬も.Ownerプロパティを設定しません。

パフォーマンスを最適化しようとしているときに留意する必要があるいくつかの点。

遅延読み込みはありません。どうすればよいですか。

これは変装した祝福として見ることができます。もちろん、すべてを手動でロードするのは面倒です。ただし、これによってdbへの呼び出し回数が減り、データをいつロードすべきかを考える必要があります。 1つのデータベースにロードできる数が多いほど、優れています。それはいつも真実でした、しかしそれはEFのこの「特徴」で今強制されます。

もちろん、if(!ObjectReference.IsLoaded)ObjectReference.Load();を呼び出すことができます。必要であれば、しかしもっと良いやり方はあなたがワンショットであなたが必要とすることを知っているオブジェクトをフレームワークにロードさせることです。これが、パラメータ化されたインクルードについての議論が意味を成し始めるところです。

あなたは犬のオブジェクトを持っていると言うことができます

public class Dog
{
    public Dog Get(int id)
    {
        return YourContext.DogSet.FirstOrDefault(it => it.ID == id );
    }
}

これはあなたがいつも使っている機能のタイプです。それはいたるところから呼び出され、そのDogオブジェクトを取得したら、さまざまな機能でそれに対してまったく異なることをします。最初に、それは事前にコンパイルされるべきです、あなたがそれを非常に頻繁に呼ぶので。第二に、それぞれの異なるページは、Dogデータの異なるサブセットにアクセスしたいと思うでしょう。所有者、FavoriteToyなどが必要な人もいます。

もちろん、必要なときはいつでもLoad()を呼び出すことができます。しかし、それは毎回データベースへの呼び出しを生成します。悪いアイデア。その代わり、各ページは最初にDogオブジェクトを要求したときに見たいデータを要求します。

    static public Dog Get(int id) { return GetDog(entity,"");}
    static public Dog Get(int id, string includePath)
{
        string query = "select value o " +
            " from YourEntities.DogSet as o " +

人気のある回答

「シングルトンアクセス」などの上記の情報をすべて使用しないでください。スレッドセーフではないため、このコンテキストを絶対に100%保存して再利用することはできません。



Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ