EF code-first: How to load related data (parent-child-grandchild)? - entity-framework

I have this entity:
public class DynamicPage {
public int PageId { get; set; }
public int Order { get; set; }
public string MenuText { get; set; }
public string MenuHover { get; set; }
public int? ParentId { get; set; }
public virtual DynamicPage Parent { get; set; }
public virtual ICollection<DynamicPage> Children { get; set; }
}
This entity may have 3 level: Parent -> Child -> Grandchild. How can I load the Parent (level 1) whit all associated children (level 2) and for each child, associated grandchild (level 3) if any? Thanks to help.

EF 4.1 feature and syntax:
var entity = context.Parents
.Include(p => p.Children.Select(c => c.GrandChildren))
.FirstOrDefault(p => p.Id == 1); // or whatever condition

If you want to make life easy on yourself, follow the EF Code First conventions of naming your table IDs simply Id (or, alternatively, name of table + Id, e.g., DyanmicPageId).
This should leave you with something like this:
public class DynamicPage
{
public int Id { get; set; }
public int Order { get; set; }
public string MenuText { get; set; }
public string MenuHover { get; set; }
public int? ParentId { get; set; }
public virtual DynamicPage Parent { get; set; }
public virtual ICollection<DynamicPage> Children { get; set; }
}
Then you need to set up the relationship between parents and children explicitly in an OnModelCreating method in your DbContext class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<DynamicPage>()
.HasMany(page => page.Children)
.WithRequired(child => child.Parent)
.HasForeignKey(child => child.ParentId);
}
You can then select children or grandchildren as needed:
var parent = dbContext.DynamicPages.Where(page => page.ParentId == null);
var children = parent.Children;
var grandchildren = parent.SelectMany(page => page.Children);
var allRelatedPages = parent.Union(children).Union(grandchildren);

Related

EF Core Include() doesn't query all childs

I have this model:
public class RepairRequest
{
[Key]
public int Id { get; set; }
public List<RepairAction> RepairActions { get; set; }
public decimal TotalPrice => RepairActions.Sum(r => r.ActionPrice);
public string LastOperation => RepairActions.LastOrDefault().RepairOperation.Description;
}
public class RepairAction
{
[Key]
public int Id { get; set; }
public int RepairRequestId { get; set; }
public RepairRequest RepairRequest { get; set; }
public int RepairOperationId { get; set; }
public RepairOperation RepairOperation { get; set; }
public decimal ActionPrice { get; set; }
}
public class RepairOperation
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
}
I'm trying to query RepairRequests and get TotalPrice and also LastOperation in a List but doesn't work for both properties. This is what I have tried till now:
using (var context = new ServiceManagerContext(new DbContextOptions<ServiceManagerContext>())) {
var data = context.RepairRequests
.Include(r => r.RepairActions).ThenInclude(r => r.RepairOperation); // Only LastAction works
//.Include("RepairActions").Include("RepairActions.RepairOperation"); // Only LastAction works
//.Include(r => r.RepairActions); // Only TotalPrice works
//.Include("RepairActions"); // Only TotalPrice works
var repairRequest = data.FirstOrDefault(r => r.Id == 5);
Assert.NotNull(repairRequest);
Assert.Equal(60.0m, repairRequest.RepairPrice);
Assert.Equal("Παραδόθηκε", repairRequest.LastAction);
}
Thank you.
I'd consider avoiding attempting to resolve calculated properties in your domain entities and instead look to resolve those when querying the data to populate view models.
If your view model needs the TotalPrice and LastOperation, then provided a Repository or such returning IQueryable you can expand the query to return what is needed using deferred execution rather than attempting to rely on eager loading the entire tree:
I.e.
IQueryable<RepairRequest> requests = context.RepairRequests.Where(x => x.Id == 5); // Or pull from a Repository returning the IQueryable
var viewModelData = requests.Select(x => new {x.Id, TotalPrice = x.RepairActions.Sum(), LastOperation = x.RepairActions.LastOrDefault()?.RepairOperation?.Description }).SingleOrDefault();
This should execute a more optimized query and return you an anonymous type with just the data you need to populate whatever view model you want to display. The iffy bit is around situations where there are no repair actions, or a repair action without an operation.. EF should avoid the null ref and just return null. the ?. syntax may not be necessary or supported, so it may just need to be ".". Using a method where you eager or lazy load those related entities and execute Linq off the entity instances, be careful around .SingleOrDefault() and drilling down into child fields.
Firstaball you have to declare Foreign Keys, and flag virtual properties like :
public class RepairRequest
{
[Key]
public int Id { get; set; }
public virtual ICollection<RepairAction> RepairActions { get; set; }
public decimal TotalPrice => RepairActions.Sum(r => r.ActionPrice);
public string LastOperation => RepairActions.LastOrDefault().RepairOperation.Description;
}
public class RepairAction
{
[Key]
public int Id { get; set; }
public decimal ActionPrice { get; set; }
public int RepairRequestId { get; set; }
[ForeignKey("RepairRequestId ")]
public virtual RepairRequest RepairRequest { get; set; }
public int RepairOperationId { get; set; }
[ForeignKey("RepairOperationId")]
public RepairOperation RepairOperation { get; set; }
}
Then you could call this, which load all children values :
var data = context.RepairRequests.Include("RepairActions.RepairOperation");

Entity Framework Navigation Properties Are Null

I am having a problem where my Entity Framework navigation properties are null. My two models are Order and OrderLine:
class Order
{
public string CustomerId { get; set; }
public string OrderNumber { get; set; }
public ICollection<OrderLine> Lines { get; set; }
}
class OrderLine
{
public int LineNumber { get; set; }
public string OrderNumber { get; set; }
public string ProductId { get; set; }
public int Quantity { get; set; }
public Order Order { get; set; }
}
My Context class looks like this
class MyContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<OrderLine> OrderLines { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasKey(p => p.OrderNumber);
modelBuilder.Entity<OrderLine>()
.HasKey(p => new { p.OrderNumber, p.LineNumber });
modelBuilder.Entity<OrderLine>()
.HasRequired(p => p.Order)
.WithMany(p => p.Lines)
.HasForeignKey(p => p.OrderNumber);
}
}
When I run the following code, my orders load (the message box shows the correct count), but the Order.Lines collection is null.
List<Order> orders = (from o in context.Orders select o).ToList();
// This message box shows the correct number of orders
MessageBox.Show(orders.Count.ToString());
// This line crashes because orders[0].Lines is null. There are lines in the database that should be joining to orders[0]
MessageBox.Show(orders[0].Lines.Count.ToString());
I've looked at a lot of examples, and I can't figure out what I'm doing incorrectly.
You need to declare the navigation properties as virtual in order to be lazy loaded:
public class Order
{
//...
public virtual ICollection<OrderLine> Lines { get; set; }
}
public class OrderLine
{
//...
public virtual Order Order { get; set; }
}
For more info check this link to see all the requirements you need to follow.

Include children and grandchildren when selecting from database

I have the following models
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Child> Child { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<GrandChild> GrandChild { get; set; }
}
public class GrandChild
{
public int Id { get; set; }
public string Name { get; set; }
}
What Im trying to do now is select a list of parents from the database with the children and grandchildren.
Tried the following with no joy:
List<Parent> parent = new List<Parent>();
parent = db.parent.ToList();
Use the Include method:
parent = db.parent.Include(parent => parent.Child.Select(child => child.GrandChild)).ToList();
For older versions of Entity Framework, you have to use a string instead of a lambda expression:
parent = db.parent.Include("Child.GrandChild").ToList();
Or you can use the custom Include extension method I blogged about here.
in Linq to Sql you should use DataLoadOptions
var dlo = new DataLoadOptions();
dlo.LoadWith<Parent>(p => p.Child );
dlo.LoadWith<Child>(c=> c.GrandChild );
db.LoadOptions = dlo;
List<Parent> parent = new List<Parent>();
parent = db.parent.ToList();

Code First Foreign Key Configuration

I am having difficulty maintaining multiple relationships between a parent class and it's children. Can anyone tell me why I can create two child references in the parent but not a third? The code below only works when the third reference is commented out.
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public int Child1Id { get; set; }
public Child Child1 { get; set; }
public int Child2Id { get; set; }
public Child Child2 { get; set; }
//public int Child3Id { get; set; }
public Child Child3 { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
}
public class CFContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(c => c.Parent)
.WithRequiredPrincipal(p => p.Child1)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Child>()
.HasRequired(c => c.Parent)
.WithRequiredPrincipal(p => p.Child2)
.WillCascadeOnDelete(false);
//modelBuilder.Entity<Child>()
// .HasRequired(c => c.Parent)
// .WithRequiredPrincipal(p => p.Child3)
// .WillCascadeOnDelete(false);
}
}
It looks like you are trying to make a one-to-many relation from Parent to Child entity. In that case the code should look like this:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
}
You don't have to specify the relation in Fluent API as long as you are following the default conventions regarding naming of the navigation properties and foreign key. You will have to use Fluent API and/or attributes to configure relations of you use non-convention names, eg renaming ParentId some something else requires you to mark it with at [ForeignKey("Parent")] attribute.
The most common use case for using Fluent API is for disabling cascade delete (there is no way to do this with attributes).

Entity Framework Code First Many to Many Setup For Existing Tables

I have the following tables Essence, EssenseSet, and Essense2EssenceSet
Essense2EssenceSet is the linking table that creates the M:M relationship.
I've been unable to get the M:M relationship working though in EF code first though.
Here's my code:
[Table("Essence", Schema = "Com")]
public class Essence
{
public int EssenceID { get; set; }
public string Name { get; set; }
public int EssenceTypeID { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<EssenceSet> EssenceSets { get; set; }
public virtual EssenceType EssenceType { get; set; }
}
[Table("EssenceSet", Schema = "Com")]
public class EssenceSet
{
public int EssenceSetID { get; set; }
public int EssenceMakerID { get; set; }
public string Name { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<Essence> Essences { get; set; }
}
[Table("Essence2EssenceSet", Schema = "Com")]
public class Essence2EssenceSet
{
//(PK / FK)
[Key] [Column(Order = 0)] [ForeignKey("Essence")] public int EssenceID { get; set; }
[Key] [Column(Order = 1)] [ForeignKey("EssenceSet")] public int EssenceSetID { get; set; }
//Navigation
public virtual Essence Essence { get; set; }
public virtual EssenceSet EssenceSet { get; set; }
}
public class EssenceContext : DbContext
{
public DbSet<Essence> Essences { get; set; }
public DbSet<EssenceSet> EssenceSets { get; set; }
public DbSet<Essence2EssenceSet> Essence2EssenceSets { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});
}
}
This is the code I'm trying to run:
Essence e = new Essence();
e.EssenceTypeID = (int)(double)dr[1];
e.Name = dr[2].ToString();
e.DescLong = dr[3].ToString();
//Get Essence Set
int setID = (int)(double)dr[0];
var set = ctx.EssenceSets.Find(setID);
e.EssenceSets = new HashSet<EssenceSet>();
e.EssenceSets.Add(set);
ctx.Essences.Add(e);
ctx.SaveChanges();
And here's the error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception.
I'm not able to find the problem. I'd greatly appreciate help setting this up right.
Thanks!
Remove your Essence2EssenceSet model class. If junction table contains only keys of related entities participating in many-to-many relations it is not needed to map it as entity. Also make sure that your fluent mapping of many-to-many relations specifies schema for table:
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet", "Com");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});