Retrieve all items that are in nested hierarchy - entity-framework

My app allows users to assign categories to their items. The classes look like this.
class Item
{
public string Id { get; set; }
/* ... */
public virtual IEnumerable<Category> Categories { get; set; }
}
class Category
{
public string Id { get; set; }
public virtual Category Parent { get; set; }
public virtual IEnumerable<Category> Subcategories { get; set; }
}
As you can see from the above there are are a hierarchy between categories.
If I have the following category tree:
|-- Item 1
|---- Child 1
|---- Child 2
|---- Child 3
|-- Item 2
And the user wants to display Item1, I want to include all the categories for Child 1, 2 and 3 in the result, i.e. four categories should be included (Item 1, Child 1, Child 2, Child 3) in the query.
How can I do this with EntityFrameworkCore. I'm using SQLite as a backend but would prefer to do this without SQL if possible.

Have you tried using the ToString() method of DbFunction
ToString() will print the current object. So, It's children will also be printed.
you need to override this method in Item class.

You may fetch grandchildren like this:
Considering eager load
public List<Category> GetCategories(int itemId)
{
Category categoryChildren = _context.Set<Category>()
.Include(i => i.Subcategories)
.ThenInclude(i => i.Category)
.FirstOrDefault(w => w.ItemId == itemId);
var categories = new List<Category>();
if (categoryChildren == null)
return categories;
// get children
categories.AddRange(categoryChildren.Subcategories.Select(s => s.Category));
// get grandchildren
foreach (var subCategory in categoryChildren.Subcategories.Select(s => s.Category))
{
_context.Entry(subCategory).Collection(b => b.Subcategories).Load();
foreach (var categoryGrandChildren in subCategory.Subcategories)
{
_context.Entry(categoryGrandChildren).Reference(b => b.Category).Load();
// check if not adding repeatables
if (!categories.Any(a => a.Id == categoryGrandChildren.Id))
categories.Add(categoryGrandChildren.Category);
}
}
return categories;
}
If you're using lazy load you don't even need .Include and .Load methods.

public void PrintAllItems() //Use Take or where to fetch you specfic data
{
var allItems = context.Items
.Include(item=> item.Categories)
.ThenInclude(cat=>cat.Subcategories)
.ToList();
foreach(var item in allItems)
{
Console.WriteLine(item.Id);
foreach(var category in item.Categoires)
{
Console.WriteLine(category.Id);
foreach(var sub in category.Subcategories)
{
Console.WriteLine(sub.Id);
}
}
}
}
public void FirstItem(string Id) //Use Take or where to fetch you specfic data
{
var allItems = context.Items
.Include(item=> item.Categories)
.ThenInclude(cat=>cat.Subcategories)
.FirstOrDefault(g=>g.Id==Id);
foreach(var item in allItems)
{
Console.WriteLine(item.Id);
foreach(var category in item.Categoires)
{
Console.WriteLine(category.Id);
foreach(var sub in category.Subcategories)
{
Console.WriteLine(sub.Id);
}
}
}
}

Entity Framework is quite convenient with all it's automation, but unfortunately, like most things in life, it has yet to master every tricky situation out there, this being one of them. (Although to be fair, the problem pretty much lies within storing hierarchical data in a relational database).
I tend to solve similar situations by "cheating" a bit, at least whenever possible/suitable, by introducing some kind of additional property/column to group them, and then simply load them all, and do the relational mapping by hand, which is usually quite simple.
Loading additional data in one database call is often to prefer before making multiple calls. (You might still have to sneak around any lurking db-admin though).
Assuming you're planning for a situation with potentially N amount in breadth and M amount in depth (if not, the other answers should suffice), it's a quick and dirty solution which in worst case at least gets the job done.
To stick with EF, the idea is essentially to first decouple the relationships that EF might have mapped and use simple value types as reference: (It's not really a necessity, but something I tend to prefer)
class Item
{
public string Id { get; set; }
public virtual IEnumerable<Category> Categories { get; set; }
}
class Category
{
public string Id { get; set; }
// We drop the parent reference property and add a simple ParentId property instead,
// hopefully saving us some future headache.
//
public string ParentId { get; set; }
//public virtual Category Parent { get; set; } // Goodbye dear friend, you have served us well.
// Depending on how you're configuring, we might have to "loose" some EF-mapped relationships,
// [NotMapped] is merely an example of that here, it's not neccessarily required.
[NotMapped]
public virtual IEnumerable<Category> Subcategories { get; set; }
// As an example, I've just added the item id as our category scope/discriminator,
// allowing us to limit our full query at least somewhat.
//
public string ItemId { get; set; }
}
Now we're ready to do what EF does best. Load and map data! We will load a plan list of the category-entities all by themselves, without any direct relationships to anything else, and then map them ourselves.
To make it maintainable, let's create a neat little static class and add some useful extensions methods that will assist us, starting with the initial DbContext-load.
public static class CategoryExtensions
{
/// <summary>
/// Extension method to find and load all <see cref="Category"/> per <see cref="Category.ItemId"/>
/// </summary>
public static List<Category> FindCategoriesForItemId(this DbContext dbContext, string itemId)
=> dbContext.Set<Category>()
.Where(c => c.ItemId == itemId)
.ToList();
}
Once we're able to easily load categories, it would be useful to be able to map the children and possibly flatten them/any subcategory if necessary, so we throw two more methods in there, one to map child categories to all the categories we've found, and one to flatten a hierarchically structure we might have in the future (or just for fun).
/// <summary>
/// Flattens the IEnumerable by selecting and concatenating all children recursively
/// </summary>
/// <param name="predicate">Predicate to select the child collection to flatten</param>
/// <returns>Flat list of all items in the hierarchically constructed source</returns>
public static IEnumerable<TSource> Flatten<TSource>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TSource>> predicate)
=> source.Concat(source.SelectMany(s => predicate(s).Flatten(predicate)));
/// <summary>
/// "Overload" for above but to use with a single root category or sub category...
/// </summary>
public static IEnumerable<TSource> Flatten<TSource>(this TSource source, Func<TSource, IEnumerable<TSource>> predicate)
=> predicate(source).Flatten(predicate);
/// <summary>
/// For each entry in the <paramref name="flatSources"/>,
/// finds all other entries in the <paramref name="flatSources"/> which has
/// a <paramref name="parentRefPropSelector"/> value matching initial entries
/// <paramref name="identityPropSelector"/>
/// </summary>
/// <param name="flatSources">Flat collection of entities that can have children</param>
/// <param name="identityPropSelector">Selector Func to select the identity property of an entry</param>
/// <param name="parentRefPropSelector">Selector Func to select the parent reference property of an entry</param>
/// <param name="addChildren">Action that is called once any children are found and added to a parent entry</param>
public static IEnumerable<TSource> MapChildren<TSource, TKey>(
this IEnumerable<TSource> flatSources,
Func<TSource, TKey> identityPropSelector,
Func<TSource, TKey> parentRefPropSelector,
Action<TSource, IEnumerable<TSource>> addChildren)
=> flatSources.GroupJoin( // Join all entityes...
flatSources, // ... with themselves.
parent => identityPropSelector(parent), // On identity property for one...
child => parentRefPropSelector(child), // ... And parent ref property for another.
(parent, children) => // Which gives us a list with each parent, and the children to it...
{
addChildren(parent, children); // ... Which we use to call the addChildren action, leaving adding up to the caller
return parent;
});
That's it. It's not perfect, but, in my opinion, it's a decent enough starter solution that still takes advantage of EF and doesn't complicate it too much. Only worry is if the amount of categories loaded becomes too large, but at that point, it will be well worth spending some actual time on a more "proper" solution. (I haven't actually tested that MapChildren-extension, and there's a lot of room for improvements to it, but I hope it helps to illustrate the idea.)
To eventually actually use it, it ends up looking something like this:
/// <summary>
/// Loads and structures all categories related to <see cref="itemId"/>
/// and returns first <see cref="Category"/> where <see cref="Category.ParentId"/>
/// is null.
/// </summary>
public Category GetMeRootCategorylore(string itemId)
{
using (var dbContext = new DbContext())
{
var mappedAndArmedCategories
= dbContext // Use our db context...
.FindCategoriesForItemId(itemId) // To find categories..
.MapChildren( // And then immediately map them, which comes close to what we're used with when using EF.
parent => parent.Id, // Set the identity property to map children against
child => child.ParentId, // Set the parent references to map with
(parent, children) => parent.Subcategories = children); // This is called when children have been found and should be mapped to the parent.
// Oh noes, what if I need a flattened category list later for whatever reason! (Or to do some real lazy loading when looking a single one up!)
// ... Aha! I almost forgot about our nifty extension method to flatten hierarchical structures!
//
var flattenedList = mappedAndArmedCategories.Flatten(c => c.Subcategories);
// Maybe we'll pick up a root category at some point
var rootCategory = mappedAndArmedCategories.FirstOrDefault(c => c.ParentId == null);
// And perhaps even flatten it's children from the single category node:
var subFlattenedList = rootCategory?.Flatten(c => c.Subcategories);
// But now we've had enough fun for today, so we return our new category friend.
return rootCategory;
}
}
Finally, here's a quite informative and helpful question about hierarchical data in relational databases if you'd like to dig in deeper or get some other ideas: What are the options for storing hierarchical data in a relational database?

Related

How to update the "LastModifiedDate" timestamp automatically on parent entity when adding/removing child entities

Is there a way to automatically enforce parent entity to be timestamped as having been modified, if any of its dependent child items are added/deleted/modified? The key word is automatically. I know this can be done by manipulating the DbEntry's EntityState or by manually setting the timestamp field in the parent, but I need this done on a number of parent-child entities in a system, so the desire is to have EF (or a related component) automatically do this somehow.
More Background and Examples
Let's say we have an Order and Order Items (1-many). When order items are added/removed from an order, the parent order itself needs to be updated to store the last modified timestamp.
public interface IModifiableEntity
{
DateTime LastModifiedOn { get; set; }
}
public class Order : IModifiableEntity
{
// some Order fields here...
// timestamp for tracking when the order was changed
public DateTime LastModifiedOn { get; set; }
// list of order items in a child collection
public ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int OrderId { get; set; }
// other order item fields...
}
Somewhere in application logic:
public void AddOrderItem(OrderItem orderItem)
{
var order = _myDb.Orders.Single(o => o.Id == orderItem.OrderId);
order.OrderItems.Add(orderItem);
_myDb.SaveChanges();
}
I already have a pattern in place to detect modified entities and set timestamps automatically via EF's SaveChanges, like this:
public override int SaveChanges()
{
var timestamp = DateTime.Now;
foreach (var modifiableEntity in ChangeTracker.Entries<IModifiableEntity>())
{
if (modifiableEntity.State == EntityState.Modified)
{
modifiableEntity.Entity.UpdatedOn = timestamp;
}
}
return base.SaveChanges();
}
That works great if any direct fields on an IModifiableEntity are updated. That entity's state will then be marked as Modified by EF, and my custom SaveChanges() above will catch it and set the timestamp field correctly.
The problem is, if you only interact with a child collection property, the parent entity is not marked as modified by EF. I know I can manually force that via context.Entry(myEntity).State or just by manually setting the LastModifiedOn field when adding child items in application logic, but that wouldn't be done centrally, and is easy to forget.
I DO NOT want to do this:
public void AddOrderItem(OrderItem orderItem)
{
var order = _myDb.Orders.Single(o => o.Id == orderItem.OrderId);
order.OrderItems.Add(orderItem);
// this works but is very manual and EF infrastructure specific
_myDb.Entry(order).State = EntityState.Modified;
// this also works but is very manual and easy to forget
order.LastModifiedOn = DateTime.Now;
_myDb.SaveChanges();
}
Any way I can do this centrally and inform EF that a "root" entity of a parent-child relationship needs to be marked as having been updated?

Linq Entry( entity.Related ).Query.Load() not writing back to Collection property

Supposing I have three EF entity classes:
public class Person {
...
public ICollection Vehicles { get; set; }
}
public class Vehicle {
...
public Person Owner { get; set; }
public CarModel ModelInfo { get; set; }
}
public class CarModel {
...
// properties for make, model, color, etc
}
The Person.Vehicles property is lazy-loaded.
Supposing I have a Person instance already loaded and I want to load its Vehicle collection property such that it also includes the related ModelInfo property.
So I have this:
void LoadVehiclesAndRelated(MyDbContext dbContext, Person person)
{
dbContext.Entry( person )
.Collection( p => p.Vehicles )
.Query()
.Include( v => v.ModelInfo )
.Load();
}
Used like so:
using( MyDbContext dbContext = ... ) {
Person p = GetPerson( 123 );
LoadVehiclesAndRelated( dbContext, p );
}
foreach(Vehicle v in p.Vehicles) {
Console.WriteLine( v.ModelInfo );
}
However when I do this, I get an exception at runtime when it first evaluates the p.Vehicles expression because the property is actually empty (so it wants to load it) but the DbContext is now disposed.
When the .Load() call was made (inside LoadVehiclesAndRelated() I saw the SQL being executed against the server (in SQL Server Profiler) but the collection property remains empty.
How can I then load the property and with the Included sub-properties?
Annoyingly, this scenario is not mentioned in the MSDN guide for explicit-loading: https://msdn.microsoft.com/en-us/data/jj574232.aspx
Looks like calling .Query().Load() is not the same as calling DbCollectionEntry.Load directly, and the important difference is that the former does not set the IsLoaded property, which then is causing triggering lazy load later. Most likely because as explained in the link, the former is intended to be used for filtered (partial) collection load scenarios.
Shortly, to fix the issue, just set IsLoaded to true after loading the collection:
var entry = dbContext.Entry(person);
var vehicleCollection = entry.Collection(p => p.Vehicles);
vehicleCollection.Query()
.Include( v => v.ModelInfo )
.Load();
vehicleCollection.IsLoaded = true;
P.S. For the sake of correctness, this behavior is sort of mentioned at the end of the Applying filters when explicitly loading related entities section:
When using the Query method it is usually best to turn off lazy loading for the navigation property. This is because otherwise the entire collection may get loaded automatically by the lazy loading mechanism either before or after the filtered query has been executed.

Is it possible to convert ObjectSet to DbSet in Entity Framework 6?

I am working on upgrading a WPF application from using .Net4/EF 4.4 to .Net4.5/EF 6.1. After the upgrade I will use DbContext (since there was no POCO-generator for ObjectContext).
The application use a Repository/UnitOfWork-pattern to access Entity Framework, and before the upgrade I could set the ObjectSet.MergeOption to OverwriteChanges (in the repository-class), but the DbSet-class does not have this feature. However, I know that I can get to a ObjectSet from the DbContext by using the IObjectContextAdapter. (See code below). But it seems that setting the MergeOption on the created ObjectSet will not reflect back to the DbSet.
So my question is this: is there any way to convert the ObjectSet back to a DbSet (conserving the MergeOption-setting)?
This is some of the repository class:
public class SqlRepository<T> : IRepository<T> where T : class, IEntity
{
protected DbSet<T> dbSet;
public SqlRepository(DbContext context)
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var set = objectContext.CreateObjectSet<T>();
set.MergeOption = MergeOption.OverwriteChanges;
dbSet = context.Set<T>();
//I would like to do something like this: dbSet = (DbSet)set;
}
}
Although not a direct answer to your question, I have come up with a T4 based solution to the EF oversight around MergeOption not being accessible in DbSet. It seems from your question this is what you are looking for?
In the default Context you have something generated by the T4 generator like:
public virtual DbSet<Person> Persons { get; set; }
public virtual DbSet<Address> Addresses { get; set; }
etc.
My approach is to edit the T4 to add Getters for each Entity that provide direct access the ObjectSet as an IQueryable:
public IQueryable<Person> GetPersons(MergeOption mergeOption = MergeOption.AppendOnly, bool useQueryImplentation = true)
{
return useQueryImplementation ? GetSet<Person>(mergeOption).QueryImplementation() : GetSet<Person>(mergeOption);
}
Then in a base DataContext
public class DataContextBase
{
/// <summary>
/// Gets or sets the forced MergeOption. When this is set all queries
/// generated using GetObjectSet will use this value
/// </summary>
public MergeOption? MergeOption { get; set; }
/// <summary>
/// Gets an ObjectSet of type T optionally providing a MergeOption.
/// <remarks>Warning: if a DataContext.MergeOption is specified it will take precedence over the passed value</remarks>
/// </summary>
/// <typeparam name="TEntity">ObjectSet entity Type</typeparam>
/// <param name="mergeOption">The MergeOption for the query (overriden by DataContext.MergeOption)</param>
protected IQueryable<TEntity> GetObjectSet<TEntity>(MergeOption? mergeOption = null) where TEntity : class
{
var set = Context.CreateObjectSet<TEntity>();
set.MergeOption = MergeOption ?? mergeOption ?? MergeOption.AppendOnly;
return set;
}
By creating a default Extension method for an IQueryable as below you can optionally add your own implenations of QueryImplementation for each table/type so that all users of your table get sorting or includes etc. (this part is not required to answer the question but its useful anyway)
So for example you could add the following to always Include Addresses when calling GetPersons()
public static class CustomQueryImplentations
{
public static IQueryable<Person> QueryImplementation(this IQueryable<Person> source)
{
return source
.Include(r => r.Addresses)
.OrderByDescending(c => c.Name);
}
}
Finally:
//just load a simple list with no tracking (Fast!)
var people = Database.GetPersons(MergeOption.NoTracking);
//user wants to edit Person so now need Attached Tracked Person (Slow)
var peson = Database.GetPersons(MergeOption.OverwriteChanges).FirstOrDefault(p => p.PersonID = 1);
//user makes changes and on another machine sometime later user clicks refresh
var people = Database.GetPersons(MergeOption.OverwriteChanges);
Or you can (as I have) write something like
Database.MergeOption = MergeOption.OverwriteChanges;
refresh loads of entities using existing Get methods but will now ALL overwrite Attached entities
Database.MergeOption = null;
Something to note is that if you load AsNoTracking before you make changes you need to either Re-Attach or probably better reload with OverwriteChanges to ensure you have the latest Entity.

How to set navigation properties that are collections using a collection of ids

I have just spent a couple of hours on this problem, and I would like to simplify it in future because I can see it being a common requirement.
I have a Question class with a navigation property that is a collection:
public class AnsweredQuestion : ModelBase
{
public virtual ICollection<Answer> Answers { get; set; }
}
All my models inherit a single base class:
public abstract class ModelBase
{
public int ID { get; set; }
}
Now I want to set the Answers collection from a collection of answer ids and I have this method in my controller - and it does work
private void SetAnswers(AnsweredQuestion question,
IEnumerable<int> newAnswerIDs)
{
//First remove any answers we don't want
question.Answers.RemoveAll(a => !newAnswerIDs.Contains(a.ID));
//Then project the current ids
IEnumerable<int> currentAnswerIds = question.Answers.Select(a => a.ID);
//Now go to the database to get the answers that match the ids that
//we have to add
IQueryable<Answer> answersToAdd = _uow.AnswerRepository.All
.Where(dbAnswers => newAnswerIDs.Contains(dbAnswers.ID)
&&
!currentAnswerIds.Contains(dbAnswers.ID));
//and add them to the navigation property
question.Answers.AddRange(answersToAdd);
}
But this code is quite complicated, and I can see me having to write it again and again in each Model where I have a navigation property.
If this was a 1 to many relationship I'd have an Answer property and an AnswerID property in my entity and the framework would resolve the issue for me. But, as far as I know, I cant do that for many to many relationships.
Can anyone think of a way to turn this into a method that can be called on any navigation property in any model? I thought about creating an extension method on a collection of models, but my stumbling block is that I need to go to the database to get the Answers that match the ids I have before I add them to the Answers collection and that would mean that my extension method would need to know which repository to use
Here is what I have come up with:
public static bool SetById<T>(this ICollection<T> collection,
IEnumerable<int> ids,
IRepository<T> repo)
where T : ModelBase
{
//First remove any answers we don't want
int count = collection.Count;
collection.RemoveAll(a => !ids.Contains(a.ID));
bool isAltered = count != collection.Count;
//Then project the current ids
IEnumerable<int> currentIds = collection.Select(a => a.ID);
IQueryable<T> toAdd = repo.All.Where(dbAnswers => ids.Contains(dbAnswers.ID) && !currentIds.Contains(dbAnswers.ID));
isAltered = isAltered || toAdd.Any();
//and add them to the navigation property
collection.AddRange(toAdd);
return isAltered;
}
This is dependent on all my entities inheriting from a base class with an ID:
public abstract class ModelBase
{
public int ID { get; set; }
}
And in my controller I call it like this (passing in my repository):
question.Answers.SetById(newAnswerIDs, _uow.AnswerRepository);

Ramifications of DbSet.Create versus new Entity()

I am a bit confused about whether to use DbSet.Create, or simply new up an entity and add it. I don't really understand the ramifications of using DbSet.Create.
I understand that DbSet.Create will create a proxied version if applicable, but I don't really understand what that means. Why do I care? It seems to me that an empty Proxied class is no more useful than a non-proxied class, since there are no related entities to lazy load.
Can you tell me the difference, beyond the obvious? And why would you care?
A scenario where using DbSet<T>.Create() makes sense is attaching an existing entity to the context and then leverage lazy loading of related entities. Example:
public class Parent
{
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
The following would work then:
using (var context = new MyDbContext())
{
var parent = context.Parents.Create();
parent.Id = 1; // assuming it exists in the DB
context.Parents.Attach(parent);
foreach (var child in parent.Children)
{
var name = child.Name;
// ...
}
}
Here lazy loading of children is triggered (perhaps with resulting empty collection, but not null). If you'd replace context.Parents.Create() by new Parent() the foreach loop will crash because parent.Children is always null.
Edit
Another example was here (populating a foreign key property of a new entity and then getting the navigation property lazily loaded after the new entity is inserted into the DB): Lazy loading properties after an insert