2017-12-11 58 views
0

私はnhibernateを初めて使ったので、これを理解できませんでした。 私は以下のクラスに類似したエンティティを持っています。nhibernateは結合の代わりに別々のクエリを強制する

public class MotherCollection 
{ 
    public virtual int Id { get; set; } 
    public virtual string Name { get; set; } 

    public virtual ISet<Class1> Collection1 { get; set; } 
    public virtual ISet<Class2> Collection2 { get; set; } 
    public virtual ISet<Class3> Collection3 { get; set; } 
    public virtual ISet<Class4> Collection4 { get; set; } 
} 

他のエンティティとの1対多の関係が多数あります。 この関係は以下のマッピングで設定します。

HasMany(d => d.Collection1).KeyColumn("McId"); 
HasMany(d => d.Collection2).KeyColumn("McId"); 
HasMany(d => d.Collection3).KeyColumn("McId"); 
HasMany(d => d.Collection4).KeyColumn("McId"); 

子クラスはsimiliaryに設定されています。

References(c1=>c1.MotherCollection).Column("McId"); 

などとなる。

私はすべての関係を取得、DBからこのエンティティを照会するとき、私はこの1つに似た巨大なクエリを取得:

SELECT * FROM MotherCollection mc 
JOIN c1 on mc.Id=c1.mcId 
JOIN c2 on mc.Id=c2.mcId 
JOIN c3 on mc.Id=c3.mcId 
JOIN c4 on mc.Id=c4.mcId 

このクエリは重複する行の多く発生し、実行に時間の多くを取ります。

私は何とか

SELECT * FROM MotherCollection Where Id = @Id 
SELECT * FROM c1 Where mcId = @Id 

と、以下のような個々のSELECTクエリにこのクエリを区切るためにNHibernateはしたいです。コレクションが遅延ロードされたときの状況と少し似ています。 私は目的のコレクションを怠惰に設定し、自分のデータレイヤーを終了する直前にFirst()プロパティにアクセスすることで、この動作を達成することができました。しかし、私はNhibernateでこれを行うもっとエレガントな方法がなければならないと思っています。

私はこれに類似したクエリを試してみた:

var data = session.QueryOver<DataSet>().Fetch(d=>d.Collection1).Eager.Fetch(d=>d.Collection2).Eager.... 

ありがとうございました。

+0

データのクエリに使用しているコードを含めることはできますか? – AlexDev

+0

私はいくつかの異なる方法を試しましたが、1つを含める – Rudithus

答えて

0

これは遅延/熱負荷と呼ばれます。複数のクエリと

1.レイジーロード:

これは、複数のクエリを生成します。あなたは、から選択するための2つの選択肢があります。遅延ロード中に、NHibernateは最初にすべての従属テーブルからすべてMotherCollectionデータとID(データなし)のみを取得するクエリを生成します。次に、従属テーブルのPrimary KeyWHERE句で新しいクエリを生成します。したがって、これは有名なN + 1の問題につながります。

これで、参照されるコレクションはデフォルトでは埋められません。 ISessionがまだ有効な間に最初にアクセスすると、それらはいっぱいになります。これはあなたの質問に記載されているようにFirst()を呼び出すことに似ています。

HasManyの設定を見てください。 LazyLoadについては言及していませんが、デフォルトです。したがって、あなたの現在のマッピングでは、これは起こっていることです。

これはNHibernateで推奨されています。

2。単一の複雑なクエリを持つ熱心な負荷は:

あなたが複数のクエリを回避し、一度にすべてのデータを取得したい場合は、以下のようなものを試してください:、参照のコレクションがいっぱいになります。これにより

HasMany(d => d.Collection1).KeyColumn("McId").Inverse().Not.LazyLoad().Fetch.Join(); 

を(データがデータベースに存在する場合)。

これはNHibernate勧告に反することに注意してください。 thisリンクを参照してください。

代わりに、我々は、デフォルトの動作を維持し、HQLleft join fetchを使用して、 特定のトランザクションのためにそれを上書きします。これは、 外部結合の を使用して、最初の選択で関心事を熱心にフェッチするようにNHibernateに指示します。 ICriteriaクエリAPIでは、 SetFetchMode(FetchMode.Join)を使用します。 Nの問題を回避するために

User user = (User) session.CreateCriteria<User>() 
       .SetFetchMode("Permissions", FetchMode.Join) 
       .Add(Expression.Eq("Id", userId)) 
       .UniqueResult(); 

完全に異なる方法:あなたがGet()またはLoad()で使用されるフェッチ戦略 を変更することができることを望むようにあなたが今まで感じた場合

は、単に 例えば、ICriteriaクエリを使用します+1を選択すると、 は第2レベルのキャッシュを使用します。

重複行とパフォーマンス

これは実際には別の問題です。これを処理する方法は複数あります。それはあなたからの追加の入力が必要になります。その前に、上記の2つのオプションの中から1つを選択する必要があります。 このように、これには新たな質問が必要です。

この回答を参照してください:https://stackoverflow.com/a/30748639/5779732

+0

私が避けようとしているのは、重複したデータで満たされた、過度に冗長なJOINクエリです。実行時間がかかります。代わりに私はマッピング中にこれを避けたいと思います。クエリを構築中にこの動作を得ることは可能ですか? – Rudithus

+0

基本的に私は内部結合と重複行なしで熱心な読み込みを行う必要があります。 今、ロードしようとすると、nhibernateは結合で満たされたクエリを生成します。私が怠惰な読み込みをしようとすると、nhibernateは私が好む振る舞いを生成しますが、コレクションを別々に初期化する必要があります。 これを達成する方法はありますか? – Rudithus

+0

私の質問の主題は熱心な/怠け者の読み込みではなく、私はすでにそれらが何であるか知っていた。私は私の質問で達成したい行動を定義していると信じています – Rudithus

0

あなたは4つの別々のクエリ、1つのコレクションをフェッチする各1を発行する必要があります。 session.Queryを使用してください。 QueryOverは古い方法です。それを使用するにはusing NHibernate.Linqを追加してください。

static public void Prefetch<T>(this IQueryable<T> query) 
{ 
    // ReSharper disable once ReturnValueOfPureMethodIsNotUsed 
    query.AsEnumerable().FirstOrDefault(); 
} 

そして使用::私は通常、プリフェッチコレクションに次の拡張メソッドを使用し

var data = session.Query<DataSet>().Fetch(d=>d.Collection1).ToList(); 
session.Query<DataSet>().Fetch(d=>d.Collection2).Prefetch(); 
session.Query<DataSet>().Fetch(d=>d.Collection3).Prefetch(); 
session.Query<DataSet>().Fetch(d=>d.Collection4).Prefetch(); 

は、コレクションにアクセスする前に4つのクエリーを実行することを確認します。あなたがそれらにアクセスすると、それらはすべて初期化されます。定期的な遅延読み込みを使用する場合は、一度に1つのオブジェクトに対して1つのコレクションを初期化します。

+0

これは私が実際にやることを避けようとしていることです。怠惰な読み込みを初期化するためだけにコレクションにアクセスすることは、ハックのようです。これをより洗練された方法で行う必要があります – Rudithus

+0

QueryOverは古い方法とは別に、これはOPが既に行っている各コレクションで 'First()'を呼び出すよりも優れていますか? –

+0

@AmitJoshi OPがどこから「First()」を呼び出すのかは不明です。コレクション上にある場合は、1つのオブジェクトのみのコレクションが初期化されます。コレクションをフェッチするクエリで呼び出すと、各オブジェクトのコレクションが初期化されます。もう一つの違いは 'AsEnumerable()'を呼び出すことで、 'select top 1 ...'としてクエリが書かれないようにすることです。コレクションが空になった場合、 'First()'は例外を生成します。 – AlexDev

関連する問題