Ich habe eine Item
. Item
hat eine Category
.
Category
hat ID
, Name
, Parent
und Children
. Parent
und Children
sind auch von Category
.
Wenn ich eine LINQ to Entities-Abfrage für eine bestimmte Methode durchführe, wird Item
die zugehörige nicht zurückgegeben Category
, es sei denn, ich verwende die Include("Category")
Methode. Aber es bringt nicht die vollständige Kategorie mit seinen Eltern und Kindern. Ich könnte es tun Include("Category.Parent")
, aber dieses Objekt ist so etwas wie ein Baum, ich habe eine rekursive Hierarchie und ich weiß nicht, wo es endet.
Wie kann ich EF dazu bringen Category
, das mit Eltern und Kindern und die Eltern mit ihren Eltern und Kindern usw. vollständig zu laden ?
Dies gilt nicht für die gesamte Anwendung. Aus Leistungsgründen wäre dies nur für diese bestimmte Entität, die Kategorie, erforderlich.
quelle
Antworten:
Anstatt die
Include
Methode zu verwenden, die Sie verwenden könntenLoad
.Sie könnten dann für jedes eine machen und alle Kinder durchlaufen und ihre Kinder laden. Dann machen Sie eine für jeden durch ihre Kinder und so weiter.
Die Anzahl der Ebenen, die Sie nach unten gehen, wird in der Anzahl für jede Schleife, die Sie haben, fest codiert.
Hier ist ein Beispiel für die Verwendung von
Load
: http://msdn.microsoft.com/en-us/library/bb896249.aspxquelle
Wenn Sie definitiv möchten, dass die gesamte Hierarchie geladen wird, würde ich versuchen, eine gespeicherte Prozedur zu schreiben, deren Aufgabe es ist, alle Elemente in einer Hierarchie zurückzugeben und das Element zurückzugeben, nach dem Sie zuerst fragen (und anschließend die untergeordneten Elemente).
Lassen Sie dann die Beziehungskorrektur der EF sicherstellen, dass alle miteinander verbunden sind.
dh so etwas wie:
// the GetCategoryAndHierarchyById method is an enum Category c = ctx.GetCategoryAndHierarchyById(1).ToList().First();
Wenn Sie Ihre gespeicherte Prozedur korrekt geschrieben haben, sollte durch das Materialisieren aller Elemente in der Hierarchie (dh
ToList()
) die EF-Beziehungskorrektur aktiviert werden.Und dann sollten für das gewünschte Element (First ()) alle untergeordneten Elemente geladen sein und die untergeordneten Elemente usw. geladen sein. Alle Elemente werden von diesem einen Aufruf einer gespeicherten Prozedur ausgefüllt, sodass auch keine MARS-Probleme auftreten.
Hoffe das hilft
Alex
quelle
EntityTableA
undEntityTableB
& Ihr Ziel ist, eine Ergebnissammlung vonEntityTableA
Entitäten mit Navigationseigenschaften fürEntityTableB
Entitäten zu haben, können Sie diese Methode NICHT verwenden. Stattdessen müssen Sie Ihren abgeflachten Datensatz entsprechend gruppieren und in die entsprechenden Entitäten konvertieren.Es könnte gefährlich sein, wenn Sie alle rekursiven Entitäten laden, insbesondere in Bezug auf die Kategorie. Sie könnten am Ende viel mehr erhalten, als Sie erwartet hatten:
Plötzlich haben Sie den größten Teil Ihrer Datenbank geladen. Sie könnten auch Rechnungszeilen, dann Kunden und dann alle anderen Rechnungen geladen haben.
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 Datenspeicher nur einmal aufrufen 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 kurzlebig wie möglich sein sollten.
quelle
Sie möchten die Hierarchie nicht rekursiv laden, es sei denn, Sie erlauben einem Benutzer, den Baum iterativ auf- oder abzusuchen: Jede Rekursionsstufe ist eine weitere Reise in die Datenbank. In ähnlicher Weise möchten Sie ein verzögertes Laden, 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: Get
Catalog
undInclude
die darin enthaltenen Elemente. Auf diese Weise erhalten Sie alle Elemente sowohl hierarchisch (Navigationseigenschaften) als auch abgeflacht. Jetzt müssen Sie nur noch die am Stamm vorhandenen Nicht-Root-Elemente ausschließen, was ziemlich trivial sein sollte.Ich hatte dieses Problem und lieferte ein detailliertes Beispiel dieser Lösung zu einem anderen, hier
quelle
Verwenden Sie diese Erweiterungsmethode, die die fest codierte Version von aufruft.
Include
Um eine dynamische Einschlussstufe zu erreichen, funktioniert sie 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()); } } }
Verwendung:
var affiliate = await DbContext.Affiliates .Include(3, a => a.Referrer) .SingleOrDefaultAsync(a => a.Id == affiliateId);
Nehmen Sie in der Zwischenzeit an der Diskussion über das EF-Repo teil.
quelle
IQueryable<TEntity>
und gilt für die verzögerte Ausführung. Sie können dies als fließende API-Abfrage und eine derInclde
Erweiterungsmethoden mehrmals für eine einzelne Abfrage verwenden, um verschiedene Zweige in die Rückgabedaten aufzunehmen.3
für denlevelIndex
Parameter gibt die Tiefenebene an, von der die untergeordneten Elemente geladen werden sollen.Sie sollten lieber eine Zuordnungstabelle einführen, die jeder Kategorie ein übergeordnetes und ein untergeordnetes Element zuordnet, anstatt die übergeordnete und untergeordnete Eigenschaft der Ladung selbst hinzuzufügen.
Je nachdem, wie oft Sie diese Informationen benötigen, können sie bei Bedarf abgefragt werden. Durch eindeutige Einschränkungen in der Datenbank können Sie vermeiden, dass unendlich viele Beziehungen möglich sind.
quelle
Und nun zu einem völlig anderen Ansatz für hierarchische Daten, zum Beispiel zum Auffüllen einer Baumansicht.
Führen Sie zunächst eine flache Abfrage für alle Daten durch und erstellen Sie dann das Objektdiagramm im Speicher:
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);
Erstellen Sie nun Ihr Diagramm:
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); } }
quelle
Hier ist eine clevere rekursive Funktion, die ich hier gefunden habe und 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; } } }
quelle
Sie können auch eine Tabellenwertfunktion in der Datenbank erstellen und diese Ihrem DBContext hinzufügen. Dann können Sie das von Ihrem Code aus aufrufen.
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; } }
quelle
Ich habe herausgefunden, dass Sie die gesamte übergeordnete Hierarchie erhalten, wenn Sie "zwei übergeordnete Ebenen" einschließen:
var query = Context.Items .Include(i => i.Category) .Include(i => i.Category.Parent.Parent)
quelle
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;
quelle
@parliament gab mir eine Idee für EF6. Beispiel für eine Kategorie mit Methoden zum Laden aller Eltern bis zum Stammknoten und aller Kinder.
HINWEIS: Verwenden Sie diese Option nur für nicht leistungskritischen Betrieb. Beispiel mit 1000 Knoten Leistung von 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; } } }
quelle
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 einzelne
entity
und alle dieseentity.Parent
Entitäten geladen werdenrecursive
.entity is same as entity.Parent
quelle
public static class EntityFrameworkExtensions { public static ObjectContext GetObjectContext(this DbContext context) { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext; } public static string GetTableName<T>(this ObjectSet<T> objectSet) where T : class { string sql = objectSet.ToTraceString(); Regex regex = new Regex("FROM (?<table>.*) AS"); Match match = regex.Match(sql); string table = match.Groups["table"].Value; return table; } public static IQueryable<T> RecursiveInclude<T>(this IQueryable<T> query, Expression<Func<T, T>> navigationPropertyExpression, DbContext context) where T : class { var objectContext = context.GetObjectContext(); var entityObjectSet = objectContext.CreateObjectSet<T>(); var entityTableName = entityObjectSet.GetTableName(); var navigationPropertyName = ((MemberExpression)navigationPropertyExpression.Body).Member.Name; var navigationProperty = entityObjectSet .EntitySet .ElementType .DeclaredNavigationProperties .Where(w => w.Name.Equals(navigationPropertyName)) .FirstOrDefault(); var association = objectContext.MetadataWorkspace .GetItems<AssociationType>(DataSpace.SSpace) .Single(a => a.Name == navigationProperty.RelationshipType.Name); var pkName = association.ReferentialConstraints[0].FromProperties[0].Name; var fkName = association.ReferentialConstraints[0].ToProperties[0].Name; var sqlQuery = @" EXEC (' ;WITH CTE AS ( SELECT [cte1].' + @TABLE_PK + ' , Level = 1 FROM ' + @TABLE_NAME + ' [cte1] WHERE [cte1].' + @TABLE_FK + ' IS NULL UNION ALL SELECT [cte2].' + @TABLE_PK + ' , Level = CTE.Level + 1 FROM ' + @TABLE_NAME + ' [cte2] INNER JOIN CTE ON CTE.' + @TABLE_PK + ' = [cte2].' + @TABLE_FK + ' ) SELECT MAX(CTE.Level) FROM CTE ') "; var rawSqlQuery = context.Database.SqlQuery<int>(sqlQuery, new SqlParameter[] { new SqlParameter("TABLE_NAME", entityTableName), new SqlParameter("TABLE_PK", pkName), new SqlParameter("TABLE_FK", fkName) }); var includeCount = rawSqlQuery.FirstOrDefault(); var include = string.Empty; for (var i = 0; i < (includeCount - 1); i++) { if (i > 0) include += "."; include += navigationPropertyName; } return query.Include(include); } }
quelle
context.YourEntitySet.RecursiveInclude(i => i.YourIncludeProperty, context)
Lassen Sie mich meine einfache Lösung anbieten, die den Anforderungen entspricht, um den Zweig hierarchischer Daten der Organisationsstruktur der ausgewählten Abteilung zu aktivieren / deaktivieren.
Die Tabelle Abteilungen sieht nach dieser SQL aus
CREATE TABLE [dbo].[Departments]( [ID] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](1000) NOT NULL, [OrganizationID] [int] NOT NULL, [ParentID] [int] NULL, [IsEnabled] [bit] NOT NULL, CONSTRAINT [PK_Departments] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
C # -Code bietet einen sehr einfachen Ansatz, der für mich gut funktioniert. 1. Es gibt die vollständige Tabelle asynchron zurück. 2. Es ändert die Eigenschaft für die verknüpften Zeilen.
public async Task<bool> RemoveDepartmentAsync(int orgID, int depID) { try { using (var db = new GJobEntities()) { var org = await db.Organizations.FirstOrDefaultAsync(x => x.ID == orgID); // Check if the organization exists if (org != null) { var allDepartments = await db.Departments.ToListAsync(); // get all table items var isExisting = allDepartments.FirstOrDefault(x => x.OrganizationID == orgID && x.ID == depID); if (isExisting != null) // Check if the department exists { isExisting.IsEnabled = false; // Change the property of visibility of the department var all = allDepartments.Where(x => x.OrganizationID == orgID && x.ID == isExisting.ID).ToList(); foreach (var item in all) { item.IsEnabled = false; RecursiveRemoveDepartment(orgID, item.ID, ref allDepartments); // Loop over table data set to change property of the linked items } await db.SaveChangesAsync(); } return true; } } } catch (Exception ex) { logger.Error(ex); } return false; } private void RecursiveRemoveDepartment(int orgID, int? parentID, ref List<Department> items) { var all = items.Where(x => x.OrganizationID == orgID && x.ParentID == parentID); foreach (var item in all) { item.IsEnabled = false; RecursiveRemoveDepartment(orgID, item.ID, ref items); } }
Dieser Ansatz funktioniert sehr schnell für relativ kleine Datensätze, ich schätze weniger als 100000. Wahrscheinlich müssen Sie für große Datenmengen serverseitig gespeicherte Funktionen implementieren.
Genießen!
quelle