Why relations one-to-many gives me null (Entity framework)? - entity-framework

I have the following application database context:
public class ApplicationContext : DbContext
{
private readonly string _connectionString;
public ApplicationContext(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("Recipes");
}
public DbSet<User> Users { get; set; }
public DbSet<Recipe> Recipes { get; set; }
public DbSet<Ingredient> Ingridients { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_connectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Ingredient>()
.HasOne(x => x.Recipe)
.WithMany(y => y.Ingredients);
}
}
My business model is simple: one recipe has many ingredients and one ingredient has only one receipt.
Models
Recipe:
public class Recipe
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public List<Ingredient> Ingredients { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public Recipe()
{
CreatedAt = DateTime.UtcNow;
UpdatedAt = DateTime.UtcNow;
}
}
Ingredient
public class Ingredient
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int ClientId { get; set; }
public string Name { get; set; }
public decimal Count { get; set; }
public string Measure { get; set; }
public int Number { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public Recipe Recipe { get; set; }
public Ingredient()
{
CreatedAt = DateTime.UtcNow;
UpdatedAt = DateTime.UtcNow;
}
}
And here I'm trying to get ingredients collection from a recipe:
List<Recipe> recipes;
recipes = _applicationContext.Recipes.Where(r => r.Category.ClientId == id).ToList();
But ingredients is always null. I don't understand why. Here is the result:
What is wrong?

You need to include the child properties in the query or else you will get null back from them. This is done to keep performance fast with Entity Framework. If all child properties were automatically included, many unnecessary joins could be generated in the sql queries that EF generates which can be a pretty large performance hit (and if that were the case, we would likely have a .Exclude extension method!)
i.e:
List<Recipe> recipes = _applicationContext.Recipes.Include(x => x.Ingredients).Where(r => r.Category.ClientId == id).ToList();

Additional to what #GregH said, you can mark the Ingredients as virtual so they can be lazy loaded.
public class Recipe
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual List<Ingredient> Ingredients { get; set; } // marked as virtual
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public Recipe()
{
CreatedAt = DateTime.UtcNow;
UpdatedAt = DateTime.UtcNow;
}
}
Now you can either eager-load them as described in the accepted answer or lazy-load them like you tried to do it before.

If you want navigation properties to Lazy Load in, I think you need to make them virtual.
public virtual ICollection<Ingredient> Ingredients { get; set; }
Or you could include them in your query so they're fetched at the same time as the recipies.
recipesWithIngredients = _applicationContext.Recipes
.Include(r => r.Ingredients)
.Where(r => r.Category.ClientId == id)
.ToList();

Related

Referenced object is not loaded from database

This the table structure I have:
#region Tables
public class WorkoutProfile
{
public WorkoutProfile()
{
WorkoutExercises = new List<WorkoutExercise>();
}
[Key]
public int ProfileId { get; set; }
public string Name { get; set; }
public int Sets { get; set; }
public int RestAfterSetInSeconds { get; set; }
public virtual User User { get; set; }
public virtual ICollection<WorkoutExercise> WorkoutExercises { get; set; }
}
public class WorkoutExercise
{
[Key]
public int WorkoutId { get; set; }
public virtual Exercise Exercise { get; set; }
public int Order { get; set; }
public int WorkoutTimeInSeconds { get; set; }
public int RestAfterInSeconds { get; set; }
}
public class Exercise
{
[Key]
public long ExerciseId { get; set; }
public string Title { get; set; }
public string Visualisation { get; set; }
public bool IsDefault { get; set; } // Is exersice should be included when user first registers
}
public class User
{
[Key]
public long UserId { get; set; }
public string Email { get; set; }
public DateTime Registered { get; set; }
}
#endregion Tables
In the repository class I run the following linq query:
return context
.WorkoutProfiles.Include(w => w.WorkoutExercises)
.Where(q => q.User.UserId == userId && q.ProfileId == profileId)
.FirstOrDefault();
and I receive the good and old "Object reference not set to an instance of an object". When examining the result, see that Exercises property in WorkoutExercises is null.
This is how the database is created using code first approach:
So, the question is: why Exercises not included in WorkoutExercises object? Do I need to include it somehow? I am using .NET Core 2
The simple answer would be no lazy loading in EFCore. Not Released yet but if you want to dabble with alpha code, its in the repository. Based on your classes there are no collections for exercises in WorkoutExcercise.
Then you need to ThenInclude(w => w.Exercises) following your Include clause since EFCore doesn't do lazy loading.
I found a solution following this post
Altered my code as following:
var top = context
.Set<WorkoutProfile>()
.Where(q => q.ProfileId == profileId && q.User.UserId == userId)
.Include(q => q.WorkoutExercises)
.SingleOrDefault();
context
.Entry(top)
.Collection(e => e.WorkoutExercises)
.Query()
.OfType<WorkoutExercise>()
.Include(e => e.Exercise)
.Load();
And it worked

EF Core could not be translated and will be evaluated locally

I have a query in EF Core 1.1.2 that is evaluated on client side and would like to know if there is a better way to translate it into sql?
The query:
from l in _ctx.Locations
join i in _ctx.Inventories on l.Id equals i.LocationId
join it in _ctx.Items on i.ItemId equals it.Id
where l.ProjectId == projectid
group i by new {l.Id, l.LHA} into il
select new InventoryLocations() {
Id= il.Key.Id,
LHA = il.Key.LHA,
FlaggedItems = il.Any(x=>x.Item != null && x.Item.Flagged)
}
If not, what other options do I have?
As I know there's still no way mapping views.
FromSQL() method can return types already known in the context only and I can not mark one model as [NotMapped] for example.
Moving back to ef6 is not an option because .net core is the target framework.
Models:
public class Location
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string Name { get; set; }
public string LHA { get; set; }
[ForeignKey("ScanUser")]
public Guid? ScanUserId { get; set; }
public User ScanUser { get; set; }
[ForeignKey("CheckUser")]
public Guid? CheckUserId { get; set; }
public User CheckUser { get; set; }
[ForeignKey("GroupLeader")]
public Guid? GroupLeaderId { get; set; }
public User GroupLeader { get; set; }
public int State { get; set; }
}
public class Inventory
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string EANCode { get; set; }
[ForeignKey("Location")]
public Guid LocationId { get; set; }
public Location Location { get; set; }
public Double ScanQty { get; set; }
[ForeignKey("ScanUser")]
public Guid? ScanUserId { get; set; }
public User ScanUser { get; set; }
public DateTime? ScanDate { get; set; }
[ForeignKey("Item")]
public Guid? ItemId { get; set; }
public Item Item { get; set; }
[ForeignKey("InventoryTask")]
public Guid? InventoryTaskId { get; set; }
public InventoryTask InventoryTask { get; set; }
[ForeignKey("CheckUser")]
public Guid? CheckUserId { get; set; }
public User CheckUser { get; set; }
public DateTime? CheckDate { get; set; }
public Double PrevQty { get; set; }
}
public class Item
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string ItemNo { get; set; }
public string EANCode { get; set; }
public string Name { get; set; }
public Double Price { get; set; }
public bool Deleted { get; set; }
public DateTime ChangeTime { get; set; }
public Double BaseQty { get; set; }
public bool Flagged { get; set; }
}
Currently (and looks like also in the incoming EF Core v.2.0) the GroupBy queries are processed locally, so the key is to avoid them where possible.
And your query seems to be eligible for that - there is no need to first multiply the data set with joins and then group it back.
I've noticed you use only reference navigation properties and FKs in your entities, basically like database table record and SQL. But EF allows you to define also a corresponding collection navigation properties which allow you to start queries from the logical root, thus eliminating the need of joins and group by.
If you define navigation property from Location to Inventory
public class Location
{
// ...
public ICollection<Inventory> Inventories { get; set; }
}
then the equivalent query could be simply:
from loc in _ctx.Locations
where loc.ProjectId == projectid
select new InventoryLocations()
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = loc.Inventories.Any(inv => inv.Item != null && inv.Item.Flagged)
}
which will be fully translated to SQL.
If for some reason you can't create the above collection navigation property, still you can start with locations and manually correlate them with inventories:
from loc in _ctx.Locations
where loc.ProjectId == projectid
select new InventoryLocations()
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = _ctx.Inventories.Any(inv => loc.Id == inv.LocationId && inv.Item != null && inv.Item.Flagged)
}
If you add the navigation property as Ivan correctly suggests:
public class Location
{
// ...
public ICollection<Inventory> Inventories { get; set; }
}
Then you can simply create a query like this:
var locations = _ctx.Locations
.Include(x => x.Inventories)
.ThenInclude(x => x.Item)
.Where(x => x.ProjectId == projectId)
.Select(loc => new InventoryLocations
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = loc.Inventories.Any(inv => inv.LocationId == loc.Id && inv.Item?.Flagged)
});

EF Core Returns one Record where Many are Expected when Using Foreign Key Relationship

I have a database that stores data regarding Facilities, Doctors, and revenue for both of the previous items - FacilityRevenue and DoctorRevenue. There are also FaciltyMaster and DoctorMaster tables that have a one to many relationship with the FacilityRevenue and DoctorRevenue tables. That is, one doctor or facility master record is related to many DoctorId or FacilityId records in the FacilityRevenue and DoctorRevenue tables. I've attempted to place foreign key relationships so that DoctorId on DoctorRevenue relates to DoctorId on DoctorMaster and FacilityId on FacilityRevenue relates to FacilityId on FaclityMaster. However, I'm not confident that Entity Framework is reading this as such.
The model for each is as follows:
public partial class FacilityMaster
{
public FacilityMaster()
{
DoctorRevenue = new HashSet<DoctorRevenue>();
FacilityRevenue = new HashSet<FacilityRevenue>();
}
[Key]
public int FacilityId { get; set; }
public string FacilityName { get; set; }
public virtual ICollection<DoctorRevenue> DoctorRevenue { get; set; }
public virtual ICollection<FacilityRevenue> FacilityRevenue { get; set; }
}
public partial class DoctorMaster
{
public DoctorMaster()
{
DoctorRevenue = new HashSet<DoctorRevenue>();
}
[Key]
public int DoctorId { get; set; }
public string DoctorName { get; set; }
public string DoctorSpecialty { get; set; }
public virtual ICollection<DoctorRevenue> DoctorRevenue { get; set; }
}
public partial class DoctorRevenue
{
[Key]
public int RecordId { get; set; }
public int DoctorId { get; set; }
public int FacilityId { get; set; }
public string FacilityName { get; set; }
public string DoctorName { get; set; }
public DateTime? Date { get; set; }
public decimal? DoctorInvoices { get; set; }
public decimal? TotalRevenue { get; set; }
public virtual DoctorMaster Doctor { get; set; }
public virtual FacilityMaster Facility { get; set; }
}
public partial class FacilityRevenue
{
[Key]
public int RecordId { get; set; }
public int FacilityId { get; set; }
public string FacilityName { get; set; }
public DateTime Date { get; set; }
public decimal? TotalInvoices { get; set; }
public decimal? TotalRevenue { get; set; }
public virtual FacilityMaster Facility { get; set; }
}
I have configured, in part, my FacilityRevenueRepository as follows:
public IEnumerable<FacilityRevenue> GetFacRevenues(Int32 pageSize, Int32 pageNumber, String name)
{
var query = _context
.Set<FacilityRevenue>()
.AsQueryable()
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
if (!String.IsNullOrEmpty(name))
{
query = query.Where(item => item.FacilityName.Contains(name));
}
return query;
}
The relevant portion of my FacilityRevenueController is as follows:
[HttpGet]
[Route("GetFacilityRevenues")]
public async Task<IActionResult> GetFacilityRevenues(Int32? pageSize = 10, Int32? pageNumber = 1, String FacilityName = null)
{
var response = new ListModelResponse<FacRevViewModel>() as IListModelResponse<FacRevViewModel>;
try
{
response.PageSize = (Int32)pageSize;
response.PageNumber = (Int32)pageNumber;
response.Model = await Task.Run(() =>
{
return FacilityRevenueRepository
.GetFacRevenues(response.PageNumber, response.PageSize, FacilityName)
.Select(item => item.ToViewModel())
.ToList();
});
response.Message = String.Format("Total Records {0}", response.Model.Count());
}
catch (Exception ex)
{
response.DidError = true;
response.ErrorMessage = ex.Message;
}
return response.ToHttpResponse();
}
The DbContext is as follows:
public partial class ERPWAGDbContext : DbContext
{
public ERPWAGDbContext(DbContextOptions<ERPWAGDbContext> options)
:base(options)
{ }
public DbSet<DoctorMaster> Doctors { get; set; }
public DbSet<FacilityMaster> Facilities { get; set; }
public DbSet<DoctorRevenue> DoctorRevenue { get; set; }
public DbSet<FacilityRevenue> FacilityRevenue { get; set; }
}
When I run this using dotnet run, Postman returns just one record for GetFacilityRevenues, where several hundred are expected.
How do I ensure that all records for a given facility are returned, and likewise for doctors, when my GetFacilities and GetDoctors API methods are called?

Get multiple tables data through Entity Framework with Generic Repository and Unit Of work

I am working on Web-API project and using Entity Framework with Generic Repository and Unit Of work. Basically i follow a tutorial for this.
Here is my table architecture.
Entity
public class ProductEntity
{
public int ProductId { get; set; }
public string ProductCode { get; set; }
public string ProductName { get; set; }
public string ProductDescription { get; set; }
public string ProductImgName { get; set; }
public bool IsActive { get; set; }
public int PrimaryCatId { get; set; }
public int SecondaryCatId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public System.DateTime CreateDate { get; set; }
public List<PrimaryProductEntity> objPrimaryProduct { get; set; }
public List<SecondaryProductEntity> objSecondaryProduct { get; set; }
}
public class PrimaryProductEntity
{
public int PrimaryCatId { get; set; }
public string PrimaryCatName { get; set; }
}
public class SecondaryProductEntity
{
public int SecondaryCatId { get; set; }
public string SecondaryCatName { get; set; }
public int PrimaryCatId { get; set; }
}
Services Code
public IEnumerable<BusinessEntities.ProductEntity> GetAllProducts()
{
var products = _unitOfWork.ProductRepository.GetAll().ToList();
var primaryProducts = _unitOfWork.PrimaryProductRepository.GetAll().ToList();
var secondaryProducts = _unitOfWork.SecondaryProductRepository.GetAll().ToList();
if (products.Any())
{
Mapper.CreateMap<tblProduct, ProductEntity>();
var proInfo = from P in products
join PP in primaryProducts on P.PrimaryCatId equals PP.PrimaryCatId
join SP in primaryProducts on P.SecondaryCatId equals SP.SecondaryCatId
select P;
var productsModel = Mapper.Map<List<tblProduct>, List<ProductEntity>>(proInfo);//getting error
return productsModel;
}
return null;
}
i know my implementation is wrong, i don't know what to write in code for fetch data from multiple tables. Please help me.
Required Data
ProductID,ProductName, PrimaryCatName, SecondaryCatName,Price, Quantity
Your Product Entity class Doesn't require a List<PrimaryProductEntity> and List<SecondaryProductEntity>. I suppose according to your class diagram Each Product is associated with one PrimaryProductEntity and one SecondaryProductEntity.
Once your model class is corrected, you would be able to access the properties of the navigation. I am not so good with writing a Query the way you want. But i hope you could get an idea of what you should be doing

Why are my entities not being lazy loaded?

I'm using EF 6 and defining my database with Code First.
The following line of code returns a Transaction entity, however the EndorsementInfo navigation property is null. I've checked the database and there is definitely data for the test data. "var trans" does appear to have a valid IQueryable, but navigation property t.EndorsementInfo is null when it shouldn't be.
var trans = unitOfWork.GetRepository<Transaction>().GetAll().Where(t => t.PolicyId == command.PolicyId);
results.Transactions = new List<TransactionListItem>();
foreach (var t in trans)
{
results.Transactions.Add(new TransactionListItem
{
Id = t.Id,
EffDate = t.EffectiveDate,
EffectiveDate = t.EffectiveDate.ToShortDateString(),
TransactionType = t.TransactionType.ToStringValue(),
EndorsementType = t.TransactionType == TransactionType.Endorsement ?
t.EndorsementInfo.EndorsementType.Description : ""
});
}
Transaction Entity:
public class Transaction : EntityBase
{
[Required]
public TransactionType TransactionType { get; set; }
public long PolicyId { get; set; }
public virtual Policy Policy { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public string Description { get; set; }
public virtual Quote QuoteInfo { get; set; }
public virtual Cancellation CancellationInfo { get; set; }
public virtual NewBusiness NewBusinessInfo { get; set; }
public virtual Endorsement EndorsementInfo { get; set; }
}
Endorsement Entity
public class Endorsement : EntityBase
{
public Transaction Transaction { get; set; }
public long EndorsementTypeId { get; set; }
public virtual EndorsementType EndorsementType { get; set; }
public int EndorsementNum { get; set; }
[MaxLength(500)]
public string EndorsementDesc { get; set; }
public Decimal? Premium { get; set; }
}
Code First Fluent Configurations
public class TransactionConfiguration : EntityTypeConfiguration<Transaction>
{
public TransactionConfiguration()
{
HasOptional(t => t.QuoteInfo).WithRequired(q => q.Transaction);
HasOptional(t => t.NewBusinessInfo).WithRequired(q => q.Transaction);
HasOptional(t => t.EndorsementInfo).WithRequired(q => q.Transaction);
HasOptional(t => t.CancellationInfo).WithRequired(q => q.Transaction);
}
}
Repositories implementation of GetAll
public IQueryable<T> GetAll(string include)
{
return _set.Include(include);
}
I've checked and rechecked that everything is set up correctly. I don't know what I could be missing.
Thanks.
You are using an opened connection to execute two data readers, you need to enable the multiple result set in the connection string.
MultipleActiveResultSets=True;