web-dev-qa-db-ja.com

Entity Frameworkは再帰的な階層でどのように機能しますか? Include()は動作しないようです

Itemがあります。 ItemにはCategoryがあります。

CategoryにはIDNameParentおよびChildrenがあります。 ParentChildrenCategoryのものです。

特定のItemに対してLINQ to Entitiesクエリを実行すると、Include("Category")メソッドを使用しない限り、関連するCategoryを返しません。しかし、それは親と子を持つ完全なカテゴリーをもたらしません。 Include("Category.Parent")を実行できますが、このオブジェクトはツリーのようなものであり、再帰的な階層構造を持ち、どこで終了するかわかりません。

どうすればEFがCategoryを親と子で完全にロードし、親をその親と子でロードすることができますか?

これはアプリケーション全体の問題ではありません。パフォーマンスを考慮すると、この特定のエンティティであるカテゴリにのみ必要です。

67

Includeメソッドを使用する代わりに、Loadを使用できます。

次に、for eachを実行し、すべての子をループして、子をロードします。その後、それぞれの子供を介して、などを行います。

あなたが行くレベルの数は、あなたが持っている各ループの数にハードコードされます。

Loadの使用例を次に示します。 http://msdn.Microsoft.com/en-us/library/bb896249.aspx

21
Shiraz Bhaiji

階層全体を確実にロードする必要がある場合は、私であれば、階層内のすべてのアイテムを返し、最初に要求したもの(およびその子)を返すことを仕事とするストアドプロシージャを作成してみます。

そして、EFの関係修正により、それらがすべて接続されるようにします。

つまり、次のようなものです:

_// the GetCategoryAndHierarchyById method is an enum
Category c = ctx.GetCategoryAndHierarchyById(1).ToList().First();
_

ストアドプロシージャを正しく記述している場合、階層内のすべてのアイテム(具体的にはToList())を具体化すると、EF関係の修正が開始されます。

そして、必要なアイテム(First())はすべての子をロードし、子をロードするなどする必要があります。すべて1つのストアドプロシージャコールから読み込まれるため、MARSの問題も発生しません。

お役に立てれば

アレックス

14
Alex James

特にカテゴリで、すべての再帰的エンティティをロードした場合、危険である可能性があります。

Category > Item > OrderLine > Item
                  OrderHeader > OrderLine > Item
         > Item > ...

突然データベースのほとんどをロードしたので、請求書の行、顧客、その他のすべての請求書もロードできます。

あなたがすべきことは次のようなものです:

var qryCategories = from q in ctx.Categories
                    where q.Status == "Open"
                    select q;

foreach (Category cat in qryCategories) {
    if (!cat.Items.IsLoaded)
        cat.Items.Load();
    // This will only load product groups "once" if need be.
    if (!cat.ProductGroupReference.IsLoaded)
        cat.ProductGroupReference.Load();
    foreach (Item item in cat.Items) {
        // product group and items are guaranteed
        // to be loaded if you use them here.
    }
}

ただし、より良い解決策は、クエリを作成して結果を含む匿名クラスを構築することです。これにより、データストアを1回ヒットするだけで済みます。

var qryCategories = from q in ctx.Categories
                    where q.Status == "Open"
                    select new {
                        Category = q,
                        ProductGroup = q.ProductGroup,
                        Items = q.Items
                    };

これにより、必要に応じて辞書の結果を返すことができます。

コンテキストはできるだけ短くする必要があることを忘れないでください。

6
Brett Ryan

ユーザーがツリーを繰り返しドリルダウン/ドリルアップできるようにしている場合を除き、階層の再帰的な読み込みは行いたくありません。再帰のすべてのレベルは、データベースへの別の旅行です。同様に、ページへのレンダリング時またはWebサービスを介した送信時に階層を移動するときに、遅延DBのトリップを防ぐために遅延ロードが必要になります。

代わりに、クエリを反転します:CatalogIncludeのアイテムを取得します。これにより、すべてのアイテムが階層(ナビゲーションプロパティ)とフラット化の両方になります。そのため、ルートに存在する非ルート要素を除外する必要があります。

私はこの問題を抱えており、別のソリューションにこのソリューションの詳細な例を提供しました here

4
JoeBrockhaus

貨物自体に親と子のプロパティを追加する代わりに、各カテゴリを親と子にマッピングするマッピングテーブルを導入する必要があります。

その情報が必要な頻度に応じて、オンデマンドで照会できます。 dbの一意の制約により、無限の関係が可能になることを回避できます。

3

Includeのハードコードされたバージョンを呼び出すこの拡張メソッドを使用して、動的な深さレベルの包含を実現します。

namespace System.Data.Entity
{
  using Linq;
  using Linq.Expressions;
  using Text;

  public static class QueryableExtensions
  {
    public static IQueryable<TEntity> Include<TEntity>(this IQueryable<TEntity> source,
      int levelIndex, Expression<Func<TEntity, TEntity>> expression)
    {
      if (levelIndex < 0)
        throw new ArgumentOutOfRangeException(nameof(levelIndex));
      var member = (MemberExpression)expression.Body;
      var property = member.Member.Name;
      var sb = new StringBuilder();
      for (int i = 0; i < levelIndex; i++)
      {
        if (i > 0)
          sb.Append(Type.Delimiter);
        sb.Append(property);
      }
      return source.Include(sb.ToString());
    }
  }
}

使用法:

var affiliate = await DbContext.Affiliates
  .Include(3, a => a.Referrer)
  .SingleOrDefaultAsync(a => a.Id == affiliateId);

とにかく、その間、EFリポジトリで discussion に参加してください。

3
Shimmy

これが私が見つけた巧妙な再帰関数です here これはこれで動作します:

public partial class Category
{
    public IEnumerable<Category> AllSubcategories()
    {
        yield return this;
        foreach (var directSubcategory in Subcategories)
            foreach (var subcategory in directSubcategory.AllSubcategories())
            {
                yield return subcategory;
            }
    }
}
1
parliament

データベースにtablevalued関数を作成して、DBContextに追加することもできます。その後、コードからそれを呼び出すことができます。

この例では、nugetからEntityFramework.Functionsをインポートする必要があります。

public class FunctionReturnType
{
    public Guid Id { get; set; } 

    public Guid AnchorId { get; set; } //the zeroPoint for the recursion

    // Add other fields as you want (add them to your tablevalued function also). 
    // I noticed that nextParentId and depth are useful
}

public class _YourDatabaseContextName_ : DbContext
{
    [TableValuedFunction("RecursiveQueryFunction", "_YourDatabaseContextName_")]
    public IQueryable<FunctionReturnType> RecursiveQueryFunction(
        [Parameter(DbType = "boolean")] bool param1 = true
    )
    {
        //Example how to add parameters to your function
        //TODO: Ask how to make recursive queries with SQL 
        var param1 = new ObjectParameter("param1", param1);
        return this.ObjectContext().CreateQuery<FunctionReturnType>(
            $"RecursiveQueryFunction(@{nameof(param1)})", param1);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //add both (Function returntype and the actual function) to your modelbuilder. 
        modelBuilder.ComplexType<FunctionReturnType>();
        modelBuilder.AddFunctions(typeof(_YourDatabaseContextName_), false);

        base.OnModelCreating(modelBuilder);
    }

    public IEnumerable<Category> GetParents(Guid id)
    {
        //this = dbContext
        return from hierarchyRow in this.RecursiveQueryFunction(true)
            join yourClass from this.Set<YourClassThatHasHierarchy>()
            on hierarchyRow.Id equals yourClass.Id
            where hierarchyRow.AnchorId == id
            select yourClass;
    }
}
1
Ozzian

私の提案は

var query = CreateQuery()
    .Where(entity => entity.Id == Id)
    .Include(entity => entity.Parent);
var result = await FindAsync(query);

return result.FirstOrDefault();

単一のentityとこのすべてのentity.Parentエンティティrecursive

entity is same as entity.Parent
0
aursad

@parliamentはEF6のアイデアをくれました。ルートノードまでのすべての親とすべての子をロードするメソッドを含むカテゴリの例.

注:これは、パフォーマンスが重要でない操作にのみ使用してください。 http://nosalan.blogspot.se/2012/09/hierarchical-data-and-entity-framework-4.html からの1000ノードのパフォーマンスの例.

Loading 1000 cat. with navigation properties took 15259 ms 
Loading 1000 cat. with stored procedure took 169 ms

コード:

public class Category 
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Name { get; set; }

    public int? ParentId { get; set; }

    public virtual Category Parent { get; set; }

    public virtual ICollection<Category> Children { get; set; }

    private IList<Category> allParentsList = new List<Category>();

    public IEnumerable<Category> AllParents()
    {
        var parent = Parent;
        while (!(parent is null))
        {
            allParentsList.Add(parent);
            parent = parent.Parent;
        }
        return allParentsList;
    }

    public IEnumerable<Category> AllChildren()
    {
        yield return this;
        foreach (var child in Children)
        foreach (var granChild in child.AllChildren())
        {
            yield return granChild;
        }
    }   
}
0
Ogglas

これを試して

List<SiteActionMap> list = this.GetQuery<SiteActionMap>()
                .Where(m => m.Parent == null && m.Active == true)
                .Include(m => m.Action)
                .Include(m => m.Parent).ToList();    

if (list == null)
    return null;

this.GetQuery<SiteActionMap>()
    .OrderBy(m => m.SortOrder)
    .Where(m => m.Active == true)
    .Include(m => m.Action)
    .Include(m => m.Parent)
    .ToList();

return list;
0
tobias

そして今、階層データへのまったく異なるアプローチ、たとえばツリービューへの移入についてです。

まず、すべてのデータに対してフラットクエリを実行し、次にメモリ内にオブジェクトグラフを作成します。

  var items = this.DbContext.Items.Where(i=> i.EntityStatusId == entityStatusId).Select(a=> new ItemInfo() { 
            Id = a.Id,
            ParentId = a.ParentId,
            Name = a.Name,
            ItemTypeId = a.ItemTypeId
            }).ToList();

ルートアイテムを取得します。

 parent = items.FirstOrDefault(a => a.ItemTypeId == (int)Enums.ItemTypes.Root);

グラフを作成します。

 this.GetDecendantsFromList(parent, items);


 private void GetDecendantsFromList(ItemInfo parent, List<ItemInfo> items)
    {
        parent.Children = items.Where(a => a.ParentId == parent.Id).ToList();
        foreach (var child in parent.Children)
        {
            this.GetDecendantsFromList(child,items);
        }
    }
0
Greg Gum