Include Grandchildren in EF Query - entity-framework

Given the object hierarchy
public class Parent
{
public int Id { get; set; }
public virtual Child Child { get; set; }
}
public class Child
{
public int Id { get; set; }
public virtual GrandChild GrandChild { get; set; }
}
public class GrandChild
{
public int Id { get; set; }
}
and the DB context
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
}
One can include children and grandchildren using Lambda syntax (using System.Data.Entity) like this:
using (MyContext ctx = new MyContext())
{
var hierarchy =
from p in ctx.Parents.Include(p => p.Child.GrandChild) select p;
}
The Lambda syntax prevents breaking the query if the class names are subsequently altered. However, if Parent has an ICollection<Child> like this instead:
public class Parent
{
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
Lambda syntax no longer works. Instead, one can use the string syntax:
var hierarchy = from p in ctx.Parents.Include("Children.GrandChild") select p;
Is the string syntax the only option, or is there some alternative way to use Lambda syntax in this situation?

Update:
If you are using Entity Framework Core you should use the following syntax
var hierarchy = from p in ctx.Parents
.Include(p => p.Children)
.ThenInclude(c => c.GrandChild)
select p;

Sure, you can do
var hierarchy = from p in ctx.Parents
.Include(p => p.Children.Select(c => c.GrandChild))
select p;
See MSDN, caption Remarks, the fifth bullet.

Related

How to get Entity from DB including navigation properties and child list total amount

I have next entity
public class Objective
{
public virtual UserInfo AssignedUser { get; set; }
public int? AssignedUserID { get; set; }
public string ObjectiveText { get; set; }
public virtual ICollection<ObjectiveTask> Tasks { get; set; }
public virtual UserInfo User { get; set; }
public int UserID { get; set; }
}
One objective could has one Assigned User and one User but many Tasks.
After getting Entity from DB I map it to DTO class which looks like this
public class ObjectiveListViewModel
{
public string AssignedString { get; set; }
public string ObjectiveText { get; set; }
public int TasksCount { get; set; }
public string UserContactName { get; set; }
}
Mapping settings doesn't meter
When I do this with query like this
(from objective in context.Set<Objective>() select objective)
.Include(o => o.User)
.Include(o => o.AssignedUser)
.ToListAsync();
Everything works cool - User and Assigned User properties are loaded and no need do extra query to DB to get their data.
But I need return objectives with tasks amount.
To do this I've created a generic class
public class EntitySubCount<TEntity>
{
public TEntity Entity { get; set; }
public int GroupCount { get; set; }
}
And use it in this way
(from objective in context.Set<Objective>() select objective)
.Include(o => o.User)
.Include(o => o.AssignedUser)
.Select(o=> new EntitySubCount<Objective> {
Entity = o,
GroupCount = o.Tasks.Count })
.ToListAsync();
But User and Assigned User properties are not loaded and it require additional query to DB to get their data.
I understand that it because lazy loading.
The question is - how I can get from DB my Entity with loaded nav. properties and with count of Tasks at once?
Thank you for any help
You are close. No need for the includes if you are projecting. In this case I project to an anonymous type, but you could create a ViewModel class to project to if desired.
var objectiveList = context.Objectives
.Select(o => new
{
Entity = o,
// or you could just pick the properties:
ObjectiveText = o.ObjectiveText,
User = o.User,
AssignedUser = o.AssignedUser,
GroupCount = o.Tasks.Count
}).ToList();
EDIT: I see you already have a ViewModel(DTO). You might be looking for something like this:
var objectiveList = context.Objectives
.Select(o => new ObjectiveListViewModel
{
AssignedString = o.AssignedUser.Name,
ObjectiveText = o.ObjectiveText,
TasksCount = o.Tasks.Count
UserContactName = o.User.Name
}).ToList();

Entity Framework one to many with condition

I have a Job class
[Table("Jobs")]
public class Job
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("fID")]
public int ID { get; set; }
public virtual ICollection<Note> Notes { get; set; }
}
The note class looks like this:
[Table("Note")]
public class Note
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("fID")]
public int ID{get; set;}
[Column("fld_int_NoteTypeID")]
public int NoteTypeID { get; set; }
}
Whenever I request the Notes from a job like this:
var job= context.Jobs.Include(x => x.Notes).FirstOrDefault(x => x.ID == jobId);
I would like the query to implicitly add Where NoteTypeId == 8.
Is it possible to somehow add this clause or do I have to explicitly add it each time?
What about workaround? Add NotMapped property which filters Notes collection:
[Table("Jobs")]
public class Job
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("fID")]
public int ID { get; set; }
public virtual ICollection<Note> Notes { get; set; }
[NotMapped]
public ICollection<Note> FilteredNotes
{
get
{
return Notes.Where(m => m.NoteTypeId == 8);
}
}
}
But the problem with this design is, when you select job like var job = context.Jobs.Include(x => x.Notes).FirstOrDefault(x => x.ID == jobId);, then you load all Notes to memory, then you can access filtered notes from memory like job.FilteredNotes. But when using LazyLoading it has advantage.
var job = context.Jobs.FirstOrDefault(x => x.ID == jobId);
var notes = job.FilteredNotes.ToList();
Update
You can also try Table-per-Hierarchy (TPH) mappimg. You have to create one abstract class and derived classes:
public abstract class Note
{
public int Id { get; set; }
public int NoteTypeId { get; set; }
}
public class JobNote : Note
{
}
public class OtherNote : Note
{
}
Then override OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Note>()
.Map<JobNote>(m => m.Requires("NoteTypeId").HasValue(8))
.Map<OtherNote>(m => m.Requires("NoteTypeId").HasValue(3)); // For example
}
Please note that, I do not have enough knowledge about TPH. I am just trying to show some hint. Please read further about TPH.

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

ef get fields only in base class

public class baseEntity
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
public class ProjectEntity : baseEntity
{
public string Address { get; set; }
public string PhoneNo { get; set; }
}
public class ProcessEntity : baseEntity
{
public string TypeName { get; set; }
public int Steps { get; set; }
}
public class DBContext : DbContext
{
public DBContext() : base("DefaultConnection")
{
}
public DbSet<baseEntity> BaseEntities { get; set; }
}
DBContext db = new DBContext();
var list = from p in db.BaseEntities select p
this will get all fields from baseentity .whatever it is BaseEntity, ProcessEntity or ProjectEntity.
I want to get the fields only in BaseEntity (only ID,Name),How can I do ?
var list = from p in db.BaseEntities select new {p.ID,p.Name}
this is not what I want.Because there are a lot of fields in my project. I don't like to write the code like this
var list = from p in db.BaseEntities select new {p.ID,p.Name,p.xxx .....................}
Try:
var list = from p in db.BaseEntities.OfType<baseEntity>() select p
Yet, keep in mind the actual Sql query to be generated depends on your Inheritance Strategy (TPT/TPH/TPC).
ofType can finish this job.thank you. But there are more Fields and more Inheritance class will add to this project.
var list = from p in db.BaseEntities.OfType<baseEntity>() select p
will generate a very complex sql. I try list.toString() to get the sql and copy to the SQL Server Management Studio , the content is more than 8000 lines. How to improve the performance ?

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

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