Jak działa struktura encji z rekurencyjnymi hierarchiami? Include () wydaje się z nim nie działać

Mam Item. Item mA Category.

Category ma ID, Name, Parent oraz Children. Parent i Children są również z Category.

Gdy wykonuję zapytanie LINQ do encji dla konkretnego Item, nie zwraca ono powiązanej Category, chyba że użyję metody Include("Category"). Ale nie przynosi pełnej kategorii, z rodzicami i dziećmi. Mógłbym zrobić Include("Category.Parent"), ale ten obiekt jest czymś w rodzaju drzewa, mam rekurencyjną hierarchię i nie wiem gdzie to koniec.

Jak sprawić, aby EF w pełni załadował Category, z rodzicem i dziećmi, a rodzic z ich rodzicem i dziećmi, i tak dalej?

To nie jest coś dla całej aplikacji, ze względu na wydajność byłoby potrzebne tylko dla tej konkretnej jednostki, kategorii.

Author: Shimmy, 2009-08-20

12 answers

Zamiast używać metody Include można użyć metody Load.

Można następnie zrobić dla każdego i pętli przez wszystkie dzieci, ładowanie ich dzieci. Następnie zrobić dla każdego przez swoje dzieci, i tak dalej.

Liczba poziomów w dół przejść będzie ciężko zakodowane w liczbie dla każdej pętli masz.

Oto przykład użycia Load: http://msdn.microsoft.com/en-us/library/bb896249.aspx

 19
Author: Shiraz Bhaiji,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-27 09:08:27

Jeśli na pewno chcesz załadować całą hierarchię, to gdybym to był ja, spróbowałbym napisać procedurę składowaną, której zadaniem jest zwrócenie wszystkich elementów w hierarchii, zwracając najpierw ten, o który prosisz (a następnie jego dzieci).

A potem niech EF ' s relationship fixup upewni się, że wszystkie są podłączone.

Czyli coś w stylu:

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

Jeśli poprawnie napisałeś procedurę składowaną, zmaterializowanie wszystkich elementów w hierarchii (tj. ToList()) powinno sprawić, że EF zaczyna się naprawianie związków.

A następnie element, który chcesz (First()) powinien mieć załadowane wszystkie swoje dzieci i powinny mieć załadowane swoje dzieci itp. Wszystkie są wypełniane z tego jednego wywołania procedury składowanej, więc nie ma problemów z Marsem.

Hope this helps

Alex

 13
Author: Alex James,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-24 10:21:16

To może być niebezpieczne, jeśli zdarzy ci się załadować wszystkie byty rekurencyjne, szczególnie na kategorię, możesz skończyć z czymś więcej, niż się spodziewałeś:

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

Nagle załadowałeś większość swojej bazy danych, mogłeś również załadować linie faktur, potem klientów, potem wszystkie ich inne faktury.

To, co powinieneś zrobić, to coś w stylu:

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

Lepszym rozwiązaniem jest jednak skonstruowanie zapytania w celu zbudowania anonimowej klasy z wynikami więc wystarczy tylko raz trafić do magazynu danych.

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

W ten sposób możesz zwrócić wynik słownika, jeśli jest to wymagane.

Pamiętaj, że Twoje konteksty powinny być jak najkrótsze.

 5
Author: Brett Ryan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-31 04:16:43

Zamiast dodawać właściwość parent and child do samego ładunku, powinieneś raczej wprowadzić tabelę mapowania, która mapuje każdą kategorię jako rodzica i potomka.

W zależności od tego, jak często potrzebujesz tych informacji, można je odpytywać na żądanie. Dzięki unikalnym ograniczeniom w db możesz uniknąć nieskończonej ilości relacji.

 3
Author: Johannes Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-26 22:23:24

Nie chcesz rekurencyjnie wczytywać hierarchii, chyba że pozwalasz użytkownikowi iteracyjnie wiercić w dół/w górę drzewa: każdy poziom rekurencji jest kolejną podróżą do bazy danych. Podobnie, będziesz potrzebować leniwego ładowania, aby zapobiec dalszym wyciekom DB podczas przechodzenia przez hierarchię podczas renderowania strony lub wysyłania przez usługę internetową.

Zamiast tego Odwróć zapytanie: Get Catalog i Include elementy w nim zawarte. Spowoduje to uzyskanie wszystkich pozycji zarówno hierarchicznie (właściwości nawigacji) i spłaszczone, więc teraz wystarczy wykluczyć elementy nie-root obecne w korzeniu, co powinno być dość trywialne.

Miałem ten problem i podałem szczegółowy przykład tego rozwiązania innemu, tutaj

 3
Author: JoeBrockhaus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:05

Użyj tej metody rozszerzenia, która wywołuje kodowaną na twardo wersję Include, aby osiągnąć dynamiczny poziom głębi włączenia, działa świetnie.

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

Użycie:

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

W każdym razie, w międzyczasie, dołącz do dyskusji o tym na EF repo.

 3
Author: Shimmy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-17 07:56:23

Oto sprytna funkcja rekurencyjna, którą znalazłem tutaj, która by zadziałała:

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
Author: parliament,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-02 23:39:23

Możesz również utworzyć funkcję tablevalued w bazie danych i dodać ją do DBContext. Więc możesz to nazwać z kodu.

Ten przykład wymaga zaimportowania EntityFramework.Funkcje z 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
Author: Ozzian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-11 13:00:48

Spróbuj tego

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
Author: tobias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-02 23:41:08

@Parlament dał mi pomysł na EF6. Przykład dla kategorii z metodami załadowania wszystkich rodziców do węzła głównego i wszystkich dzieci.

Uwaga: używaj tego tylko do operacji o krytycznym znaczeniu dla wydajności. Przykład z wydajnością 1000 węzłów z 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

Kod:

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
Author: Ogglas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-14 19:35:57

Moja propozycja to

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

return result.FirstOrDefault();

A to oznacza, że załaduje pojedyncze entity i wszystkie te entity.Parent byty recursive.

entity is same as entity.Parent
 0
Author: aursad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-26 06:48:22

A teraz zupełnie inne podejście do danych hierarchicznych, na przykład wypełnianie widoku drzewa.

Najpierw wykonaj płaskie zapytanie dla wszystkich danych, a następnie zbuduj Wykres obiektu w pamięci:

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

Get the root item:

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

Teraz Zbuduj swój wykres:

 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
Author: Greg0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-19 22:49:23