it-swarm.com.de

Wie funktioniert Entity Framework mit rekursiven Hierarchien? Include () scheint damit nicht zu funktionieren

Ich habe eine Item. Item hat eine Category.

Category hat ID, Name,ParentundChildren. Parent und Children gehören ebenfalls zu Category.

Wenn ich eine LINQ to Entities-Abfrage für ein bestimmtes Item-Objekt durchführe, wird das zugehörige Category-Objekt nicht zurückgegeben, es sei denn, ich verwende die Include("Category")-Methode. Es bringt jedoch nicht die gesamte Kategorie mit Eltern und Kindern. Ich könnte Include("Category.Parent") machen, aber dieses Objekt ist so etwas wie ein Baum, ich habe eine rekursive Hierarchie und ich weiß nicht, wo es endet.

Wie kann EF veranlasst werden, das Category vollständig mit Eltern und Kindern und den Eltern mit Eltern und Kindern zu laden, und so weiter?

Dies gilt nicht für die gesamte Anwendung. Aus Gründen der Leistung wäre dies nur für diese bestimmte Entität, die Kategorie, erforderlich.

65

Anstelle der Include-Methode können Sie Load verwenden.

Sie könnten dann für jeden einen und alle Kinder durchlaufen und ihre Kinder laden. Dann machen Sie einen für jeden durch ihre Kinder und so weiter. 

Die Anzahl der Stufen, die Sie verlassen, wird in der Anzahl der Schleifen, die Sie haben, hart codiert. 

Hier ein Beispiel für die Verwendung von Load: http://msdn.Microsoft.com/en-us/library/bb896249.aspx

19
Shiraz Bhaiji

Wenn Sie definitiv möchten, dass die gesamte Hierarchie geladen wird, würde ich, wenn ich es wäre, versuchen, eine gespeicherte Prozedur zu schreiben, deren Aufgabe es ist, alle Elemente in einer Hierarchie zurückzugeben, und denjenigen zurückgeben, nach dem Sie zuerst gefragt sind (und dessen untergeordnete Elemente).

Und dann lassen Sie die Beziehungskorrektur der EF sicherstellen, dass sie alle miteinander verbunden sind.

etwas wie:

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

Wenn Sie Ihre gespeicherte Prozedur richtig geschrieben haben, sollten alle Elemente in der Hierarchie (d. H. ToList()) materialisiert werden.

Und dann sollte das gewünschte Element (First ()) alle Kinder geladen haben, und die Kinder sollten geladen sein. Alle werden von diesem einen gespeicherten Prozeduraufruf ausgefüllt, also auch keine MARS-Probleme.

Hoffe das hilft

Alex

14
Alex James

Es könnte gefährlich sein, wenn Sie alle rekursiven Entitäten laden, besonders in der Kategorie. Sie könnten am Ende mit WAY enden, als Sie erwartet hatten:

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

Plötzlich haben Sie den größten Teil Ihrer Datenbank geladen, und Sie hätten möglicherweise auch Rechnungszeilen, dann Kunden und alle anderen Rechnungen geladen.

Was Sie tun sollten, ist etwa Folgendes:

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.
    }
}

Eine bessere Lösung besteht jedoch darin, Ihre Abfrage so zu erstellen, dass eine anonyme Klasse mit den Ergebnissen erstellt wird, sodass Sie Ihren Datastore nur einmal treffen müssen.

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

Auf diese Weise können Sie bei Bedarf ein Wörterbuchergebnis zurückgeben.

Denken Sie daran, dass Ihre Kontexte so kurz wie möglich sein sollten.

5
Brett Ryan

Sie möchten kein rekursives Laden der Hierarchie durchführen, es sei denn, Sie erlauben einem Benutzer, den Baum iterativ nach unten/oben zu durchsuchen: Jede Rekursionsebene ist eine weitere Reise in die Datenbank. In ähnlicher Weise sollten Sie ein verzögertes Laden wünschen, um weitere DB-Auslösungen zu verhindern, wenn Sie beim Rendern auf eine Seite oder beim Senden über einen Webservice die Hierarchie durchlaufen.

Drehen Sie stattdessen Ihre Abfrage um: Rufen Sie Catalog und Include die darin enthaltenen Elemente ab. Dadurch erhalten Sie alle Elemente sowohl hierarchisch (Navigationseigenschaften) als auch abgeflacht. Jetzt müssen Sie nur noch die Nicht-Root-Elemente ausschließen, die sich im Stammverzeichnis befinden. Dies sollte ziemlich trivial sein. 

Ich hatte dieses Problem und gab ein detailliertes Beispiel für eine andere Lösung an, hier.

4
JoeBrockhaus

Sie können stattdessen eine Zuordnungstabelle einführen, die jeder Kategorie ein übergeordnetes Element und ein untergeordnetes Element zuordnet, anstatt die übergeordnete und untergeordnete Eigenschaft der Fracht selbst hinzuzufügen.

Je nachdem, wie oft Sie diese Informationen benötigen, können Sie diese bei Bedarf abfragen. Durch eindeutige Einschränkungen in der Datenbank können Sie verhindern, dass unendlich viele Beziehungen möglich sind.

3

Verwenden Sie diese Erweiterungsmethode, die die hartcodierte Version von Include aufruft, um eine dynamische Tiefe der Inklusion zu erreichen. Sie funktioniert hervorragend.

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());
    }
  }
}

Verwendungszweck:

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

In der Diskussion darüber auf dem EF-Repo.

3
Shimmy

Hier ist eine clevere rekursive Funktion, die ich hier die dafür funktionieren würde: 

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

Sie können auch eine Funktion mit Tabellenwert in der Datenbank erstellen und diese Ihrem DBContext hinzufügen. Dann können Sie das von Ihrem Code aus aufrufen. 

In diesem Beispiel müssen Sie EntityFramework.Functions aus .__ importieren. Nuget.

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

Mein Vorschlag wäre

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

return result.FirstOrDefault();

und es bedeutet, dass es einzelne entity und all diese entity.Parent-Entitäten recursive lädt.

entity is same as entity.Parent
0
aursad

@parlament hat mir eine Idee für EF6 gegeben. Beispiel für Kategorie mit Methoden, um alle übergeordneten Elemente bis zum Wurzelknoten und alle untergeordneten Elemente zu laden.

HINWEIS: Verwenden Sie dies nur für nicht leistungskritische Vorgänge. Beispiel mit 1000 Knotenleistung aus http://nosalan.blogspot.se/2012/09/hierarchical-data-and-entity-framework-4.html .

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

Code:

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

versuche dies

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

Und nun zu einer völlig anderen Herangehensweise an hierarchische Daten, beispielsweise zum Auffüllen einer Baumansicht.

Führen Sie zunächst eine flache Abfrage für alle Daten aus und erstellen Sie anschließend das Objektdiagramm im Arbeitsspeicher:

  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();

Holen Sie sich das Root-Element:

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

Bauen Sie jetzt Ihr Diagramm auf:

 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