8

2つの単純なPOCOクラスがあります。MyYのインスタンスを使用して、以下のプロパティをハイドレイト化しようとしていますY。私はこれを行うためにいくつかの方法を試しましたが、明白または単純なものが欠けている可能性があると思います。

public class X
{
     public int Id { get; set;}
     public virtual Y MyY { get; set; }
}

public class Y
{
     public int Id { get; set; }
     // ...
}

DbContextのコンストラクターのサブクラスで、この呼び出しを介して遅延読み込みをオフにしました。

Configuration.LazyLoadingEnabled = false;

取得するときXに私は試しました

context.Set<X>.Include("MyY").FirstOrDefault(x => ....);

うまくいきませんでした。私は試した

var result = context.Set<X>.FirstOrDefault(x => ....);
context.Entry(result).Reference("MyY").Load();

これは機能しますが、データベースへの2回のラウンドトリップが必要です。私は試した

context.Set<X>.Select(x => new { X = x, Y = x.MyY }).FirstOrDefault(x => ...);

これも機能しますが、モデルを「弱く」します(通常、新しいタイプに投影することはそれほど悪くありませんが、これらのEF POCOの「形状」は、後でWCFを介して送信するDTOに対して完全に機能します)。

別の質問への回答で提案されているように、私は最終的virtualにプロパティから削除しようとしましたが、それはまったく効果がありませんでした。MyY

最後に、汎用リポジトリパターンを使用します。私が最終的に得たのは、部分的に示されている次の設計です。これは、正しく機能するように変更すると、明示的負荷(推奨されません)と熱心な負荷をサポートします。単一のdbラウンドトリップeager-loadを取得するように変更するにはどうすればよいですか?

public class EFRepository : IRepository
{
    public T Get<T>(Specification<T> specification) where T : class, IEntity
    {
        var result = ApplyEagerLoading(context.Set<T>()).FirstOrDefault(specification.IsMatch);
        ApplyPostQueryLoading(new List<T> { result });
        return result;
    }

    // doesn't really seem to work yet...
    private DbSet<T> ApplyEagerLoading<T>(DbSet<T> set) where T : class, IEntity
    {
        var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
        foreach (var spec in ls.Where(s => !s.ExplicitLoad))
            set.Include(spec.PropertyName);
        return set;
    }

    // works, but wrong on so many levels...
    private void ApplyPostQueryLoading<T>(IEnumerable<T> entities) where T : class, IEntity
    {
        var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
        foreach (var e in entities)
            foreach (var spec in ls.Where(s => s.ExplicitLoad))
                if (spec.IsCollection)
                    context.Entry(e).Collection(spec.PropertyName).Load();
                else
                    context.Entry(e).Reference(spec.PropertyName).Load();
    }

    private readonly IDictionary<Type, IList<LoadSpec>> loadSpecs = new Dictionary<Type, IList<LoadSpec>>();

    private class LoadSpec
    {
        internal string PropertyName;
        internal bool ExplicitLoad;
        internal bool IsCollection;
    }
}

使用例:

// add a rule to load MyY explicitly
repository.AddLoadRule<X>(x => x.MyY, explicit:true, isCollection:false)
...
var x = repository.Get<X>(new Specification<X>(x => x.Id == 5));

// add a rule to load MyY with X
repository.AddLoadRule<X>(x => x.MyY, explicit:false)
...
// x.MyY will be null! Doesn't work!
var x = repository.Get<X>(new Specification<X>(x => x.Id == 5));

回答に基づく更新:

私の一時的なコード例が嘘をついていることがわかりました(上記のワンライナー)。.Include私は実際にの結果をローカル変数にキャッシュしましたが、の結果ではなく.FirstOrDefaultに対して適用しました。の修正は次のとおりです。これは、関連する質問で他の人が提案したことを反映しています。.Set<X>.IncludeApplyEagerLoading

    private IQueryable<T> ApplyEagerLoading<T>(IEnumerable<T> set) where T : class, IEntity
    {
        var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
        var query = set.AsQueryable();
        return ls.Where(s => !s.ExplicitLoad).Aggregate(query, (current, spec) => current.Include(spec.PropertyName));
    }
4

1 に答える 1

1

これは機能するはずです:

X entity = context.Set<X>().Include(x => x.MyY).FirstOrDefault();

そうでない場合、問題は他の場所にあるはずです。

熱心な読み込み戦略が必要な場合は、この回答を確認してください。

于 2011-07-06T19:37:05.050 に答える