Entity Framework Core - issue with loading related entities that also contain related entities - entity-framework-core

I am using Entity Framework Core following Chris Sakell's blog here.
He uses generics to manage his repositories and also a base repository that he uses for all the other repositories.
Part of the base repository has the the following code for the retrieval of a single entity that also downloads related entities using the includeProperties option. Here is the generic code for a retrieving a single item.
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
I am using it on a client table that has many jobs attached to it.
This is how I structured my code.
public ClientDetailsViewModel GetClientDetails(int id)
{
Client _client = _clientRepository
.GetSingle(c => c.Id == id, c => c.Creator, c => c.Jobs, c => c.State);
if(_client != null)
{
ClientDetailsViewModel _clientDetailsVM = mapClientDetailsToVM(_client);
return _clientDetailsVM;
}
else
{
return null;
}
}
The line:
.GetSingle(c => c.Id == id, c => c.Creator, c => c.Jobs, c => c.State);
successfully retrieves values for creator state and job.
However, nothing is retrieved for those related entities associated with the "jobs".
In particuar, JobVisits is a collection of visits to jobs.
For completeness I am adding the "job" and "jobvisit" entities below
public class Job : IEntityBase
{
public int Id { get; set; }
public int? ClientId { get; set; }
public Client Client { get; set; }
public int? JobVisitId { get; set; }
public ICollection<JobVisit> JobVisits { get; set; }
public int? JobTypeId { get; set; }
public JobType JobType { get; set; }
public int? WarrantyStatusId { get; set; }
public WarrantyStatus WarrantyStatus { get; set; }
public int? StatusId { get; set; }
public Status Status { get; set; }
public int? BrandId { get; set; }
public Brand Brand { get; set; }
public int CreatorId { get; set; }
public User Creator { get; set; }
....
}
public class JobVisit : IEntityBase
{
...
public int? JobId { get; set; }
public Job Job { get; set; }
public int? JobVisitTypeId { get; set; }
public JobVisitType VisitType { get; set; }
}
My question is, how do I modify the repository code above and my GetSingle use so that I can also load the related enitities JobVisit collection and the other related single entities Brand and JobType?

It is intended that navigation properties are not necessary retrieved for associated with the "jobs". That is why some properties are null. By default the .Include(property); goes only 1-level deep and that is a good thing. It prevents your query from fetching all the data of your database.
If you want to include multiple levels, you should use .ThenInclude(property) after .Include(property). From the documentation:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
My advice is that your method public T GetSingle(...) is nice and I would not change it in order to include deeper levels. Instead of that, you can simply use explicit loading. From the documentation:
using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1);
context.Entry(blog)
.Collection(b => b.Posts)
.Load();
context.Entry(blog)
.Reference(b => b.Owner)
.Load();
}

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

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

EF6 Ignoring related data

Scenario
public class Product : Entity, IAggregateRoot
{
public string Name { get; set; }
public string Dimension { get; set; }
public decimal Volume { get; set; }
public bool Featured { get; set; }
public Farm Farm { get; set; }
public int FarmId { get; set; }
/// <summary>
/// Sell Price
/// </summary>
public decimal BidPrice { get; set; }
public int QuantityAvaliable { get; set; }
public ICollection<Image> Images { get; set; }
public string Description { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
public class Category : Entity, IAggregateRoot
{
public string Title { get; set; }
public string CategoryImage { get; set; }
public Category Parent { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
Relationship setup
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
HasKey(x => x.Id);
Property(x => x.Created).HasColumnType("DateTime");
Property(x => x.Modified).HasColumnType("DateTime");
Property(x => x.BidPrice).HasColumnType("Decimal");
#region RELATIONSHIP
//BelongsTo
HasRequired(x => x.Farm);
HasRequired(x => x.Category);
HasMany(x => x.Images);
#endregion
}
So I have this two model where I need to bring the data from Product model with Category information
I have checked my database, the data is consistent, the Product record have the FK for the Category record.
but when I try to get Product Data using EF6, the category information doesnt come, I get a null object.
Because of = () =>
{
_product = _repository.Find(p => p.Id == 1, p => p.Category);
};
It should_not_be_bull = () =>
_product.Category.ShouldNotBeNull();
the response from data base is for Category is null. but the record is there.
I had it working properly before. for some random magic reason it just stop working.
THE FIND method
public virtual TEntity Find(Expression<Func<TEntity, bool>> predicate = null, params Expression<Func<TEntity, object>>[] includes)
{
var set = CreateIncludedSet(includes);
return (predicate == null) ?
set.FirstOrDefault() :
set.FirstOrDefault(predicate);
}
the CreateIncludeSet
private IDbSet<TEntity> CreateIncludedSet(IEnumerable<Expression<Func<TEntity, object>>> includes)
{
var set = CreateSet();
if (includes != null)
{
foreach (var include in includes)
{
set.Include(include);
}
}
return set;
}
the CreateSet method
private IDbSet<TEntity> CreateSet()
{
return Context.CreateSet<TEntity>();
}
MY DbContext implementation is here
https://github.com/RobsonKarls/FreedomWebApi/blob/dev/Source/Freedom.Infrastructure.DataAccess/Factories/FreedomDbContext.cs
all project is there too for further analisys
any help is valuable.
Thank you
The problem in your code is in this line in CreateIncludedSet method:
set.Include(include);
Yes, you include the data but you do not change you set. You should change it to something like:
set = set.Include(include);
Your code is a bit unclear, but try something like this....
_product = _repository.Include(p => p.Category).SingleOrDefault(x => x.Id == 1);
also see...
https://stackoverflow.com/a/7348694/6200410

Entity Framework Core (EF 7) many-to-many results always null

I have followed the instructions for the workaround for many-to-many described in Issue #1368 and the Docs Here... but when I try to navigate, it always returns null.
My Models:
public class Organization
{
public Guid OrganizationID { get; set; }
//...
public ICollection<OrganizationSubscriptionPlan> OrganizationSubscriptionPlans { get; set; }
}
public class SubscriptionPlan
{
public int SubscriptionPlanID { get; set; }
//...
public ICollection<OrganizationSubscriptionPlan> OrganizationSubscriptionPlans { get; set; }
public class OrganizationSubscriptionPlan
{
[ForeignKey("Organization")]
public Guid OrganizationID { get; set; }
public Organization Organization { get; set; }
[ForeignKey("SubscriptionPlan")]
public int SubscriptionPlanID { get; set; }
public SubscriptionPlan SubscriptionPlan { get; set; }
}
ApplicationDbContext:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<OrganizationSubscriptionPlan>().HasKey(x => new { x.OrganizationID, x.SubscriptionPlanID });
builder.Entity<OrganizationSubscriptionPlan>().HasOne(x => x.Organization).WithMany(x => x.OrganizationSubscriptionPlans).HasForeignKey(x => x.OrganizationID);
builder.Entity<OrganizationSubscriptionPlan>().HasOne(x => x.SubscriptionPlan).WithMany(x => x.OrganizationSubscriptionPlans).HasForeignKey(x => x.SubscriptionPlanID);
}
And my Query:
var organizations = _context.Organizations
.Include(o => o.OrganizationSubscriptionPlans);
foreach (var organization in organizations)
{
//....
var subscriptions = organization.OrganizationSubscriptionPlans
.Select(s => s.SubscriptionPlan);
// ^^^^^^^^^^^ why is subscriptions always null?
}
The "organizations" query returns the results as expected, including the list of OrganizationSubscriptionPlans within each one, but when I try to navigate to them in the foreach loop the "subscriptions" query returns null every time. What am I doing wrong?
Turns out it's a Lazy Loading issue. You have to "Include" the joining entity and then "ThenInclude" the other entity.
var organizations = _context.Organizations
.Include(o => o.OrganizationSubscriptionPlans)
.ThenInclude(s => s.SubscriptionPlan);
ForeignKey attr is to decorate reference properties to indicate them what primitive property hold the FK value.
public class OrganizationSubscriptionPlan
{
public Guid OrganizationID { get; set; }
[ForeignKey("OrganizationID")]
public Organization Organization { get; set; }
public int SubscriptionPlanID { get; set; }
[ForeignKey("SubscriptionPlanID")]
public SubscriptionPlan SubscriptionPlan { get; set; }
}

Only initializers, entity members, and entity navigation properties are supported when including child table in query

I've got a simple one-to-many db relationship defined by EF 6.1. The database that is generated appears correct and has the appropriate explicit relationship. However, when I try a query involving the child table, I get a NotSupportedException (see title of post). The failing code is in GetContractList (see below).
I did some digging and found some people having this problem, but those issues seemed related to attempting to include non-entity items in queries; I don't think that's what's happening here.
Anyone see what I'm doing wrong?
[Table("Contract")]
public class Contract : IContract
{
[Key,
DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ContractId { get; set; }
[StringLength(1023),
Required]
public string Name { get; set; }
public DateTime DateBegin { get; set; }
public DateTime DateEnd { get; set; }
public string PhoneNumber { get; set; }
public virtual ICollection<IMarket> Markets { get; set; }
public Contract()
{
Markets = new List<IMarket>();
}
}
[Table("Market")]
public class Market : IMarket
{
[Key, Column(Order = 0)]
public int ContractId { get; set; }
[Key, Column(Order = 1)]
public int MarketId { get; set; }
}
public IEnumerable<IIdName> GetContractList(IAffiliateContractSearchCriteria criteria)
{
var now = DateTime.UtcNow;
// This is the line throwing the exception.
return _repository.AsQueryable().Where(c => (criteria.IncludeOnlyActive
? c.DateBegin < now
&& (c.DateEnd > now || c.DateEnd <= SqlDateTime.MinValue.Value)
: true)
&& (c.Markets.Any()
? c.Markets.Select(m => m.MarketId).Any(x => criteria.MarketIds.Contains(x))
: true)).OrderBy(a => a.Name).Select(a => new IdName() { Id = a.AffiliateContractId, Name = a.Name });
}
public class ContractSearchCriteria : IContractSearchCriteria
{
public bool IncludeOnlyActive { get; set; }
public List<int> MarketIds { get; set; }
public ContractSearchCriteria()
{
IncludeOnlyActive = false;
MarketIds = new List<int>();
}
public ContractSearchCriteria(bool includeOnlyActive, int[] marketIds)
: this()
{
IncludeOnlyActive = includeOnlyActive;
MarketIds.AddRange(marketIds);
}
}
Ok, so the problem is that you can't use interfaces when defining EF Entity objects and relationships.
My bad.