Lazy loading in EFCore loads all the data even if not accessed - asp.net-core

I have been trying to use lazy loading in ASP.NET core 6, I have followed the documentation to do so. However, the behavior of the lazy loading is not the same as described in the docs
Lazy loading means that the related data is transparently loaded from the database when the navigation property is accessed. Here
builder.Services.AddDbContext<AppDbContext>(
options => options.UseLazyLoadingProxies()
.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
Currently I have the following entities
public class Book
{
public long Id { get; set; }
public string Name { get; set; }
public long AuthorId { get; set; }
public virtual Author Author { get; set; }
}
public class Author
{
public long Id { get; set; }
public string Name { get; set; }
}
And I have this endpoint
[HttpGet]
public IEnumerable<Book> GetBooks()
{
var list = _appDbContext.Books.ToList();
return list;
}
And the resulted SQL quires are
SELECT [b].[Id], [b].[AuthorId], [b].[Name]
FROM [Books] AS [b]
SELECT [a].[Id], [a].[Name]
FROM [Author] AS [a]
WHERE [a].[Id] = #__p_0
So in the endpoint I am not accessing the property Author inside Book entity, then why ef core is loading data that I dont need it kink of like eager loading but with two queries.

After investigation I realized that AutoMapper was accessing the Author so that is why its always retrieving the Author data.
So, lazyloading in EFCore is not convenient currently.

Related

Can't load One To Many navigation property in EF Core

My question may be a littler more specific and I'm just learning EF Core. I have two classes. tblBuilding and tblBuildingHour. Simple classes. Don't mind the naming convention of the tables. It's a legacy database. It works fine if I remove the navigation properties. What am I missing? Is there something I need to do for the Castle proxy configuration? Is it the composite keys? Is it the lazy loading of the navigation property? I'm stumped.
public class tblBuilding {
public int ID {get;set;}
public string Name {get;set;}
public virtual ICollection<tblBuildingHour> BuildingHours {get;set;}
}
public class tblBuildingHour {
public int BuildingID {get;set;}
public DateTime BuildingHourDate { get;set; }
public DateTime StartTime {get;set;}
public DateTime EndTime {get;set;
public virtual Building Building {get;set;}
}
There's lazy loading of the entities in my db context.
services.AddDbContext<EMSDataContext>(options => options.UseLazyLoadingProxies()
.UseSqlServer(Configuration.GetSection(EmsDevDb).Value)
.UseLoggerFactory(_loggerFactory));
In the dbContext I added the one to many relationship.
modelBuilder.Entity<tblBuilding>()
.HasMany(b => b.BuildingHours)
.WithOne(r => r.Building)
.HasForeignKey(r => r.BuildingID);
The only odd thing in this tblBuildingHour table is that it has a composite keys so I don't know if that's what's messing it up.
modelBuilder.Entity<tblBuildingHour>()
.HasKey(c => new { c.BuildingID, c.BuildingHourDate });
I wondered if the lazyloading was affecting it so I tried this to no avail.
https://www.learnentityframeworkcore.com/lazy-loading
EF Core creates the relationships provided you create your entity classes correctly. After looking closer at my classes, I removed the fluent api relationship, changed my entity classes to have List instead of ICollection properties, removed .Include from my repo queries, and it worked like a charm. Eager loading wasn't the solution. Surprisingly enough, it wasn't even the composite keys. EF Core managed to create the relationships correctly after I removed mine.
Addendum: to be clear, removing .Include is not part of the fix, that was only for lazy loading.
REMOVED
modelBuilder.Entity<tblBuilding>()
.HasMany(b => b.BuildingHours)
.WithOne(r => r.Building)
.HasForeignKey(r => r.BuildingID);
UPDATED
[Table("tblBuilding")]
public class EMSBuilding
{
public int ID { get; set; }
public string Name{ get; set; }
public virtual List<EMSBuildingHour> Hours { get; set; }
}
[Table("tblBuildingHours")]
public class EMSBuildingHour
{
[Column("BuildingHoursDate")]
public DateTime BuildingHourDate { get; set; }
public DateTime OpenTime { get; set; }
public DateTime CloseTime { get; set; }
public int BuildingID { get; set; }
public virtual EMSBuilding Building { get; set; }
}
modelBuilder.Entity<EMSBuildingHour>()
.HasKey(c => new { c.BuildingID, c.BuildingHourDate });

Create a Parent with existing children in EntityFramework core

I am building a Web API and have two models: Task and Feature:
public class Feature
{
[Key]
public long FeatureId { get; set; }
public string Analyst_comment { get; set; }
public virtual ICollection<User_Task> Tasks { get; set; }
public Feature()
{
}
}
public class User_Task
{
[Key]
public long TaskId { get; set; }
public string What { get; set; }
[ForeignKey("FeatureId")]
public long? FeatureId { get; set; }
public User_Task()
{
}
}
I create Tasks first and then create a Feature that combines few of them. Task creation is successful, however while creating a Feature with existing Tasks, my controller throws an error saying the task already exists:
My FeatureController has following method:
//Create
[HttpPost]
public IActionResult Create([FromBody] Feature item)
{
if (item == null)
{
return BadRequest();
}
** It basically expects that I am creating a Feature with brand new tasks, so I guess I will need some logic here to tell EF Core that incoming tasks with this feature already exist **
_featureRepository.Add(item);
return CreatedAtRoute("GetFeature", new { id = item.FeatureId }, item);
}
How to tell EF core that incoming Feature has Tasks that already exist and it just needs to update the references instead of creating new ones?
My context:
public class WebAPIDataContext : DbContext
{
public WebAPIDataContext(DbContextOptions<WebAPIDataContext> options)
: base(options)
{
}
public DbSet<User_Task> User_Tasks { get; set; }
public DbSet<Feature> Features { get; set; }
}
And repo:
public void Add(Feature item)
{
_context.Features.Add(item);
_context.SaveChanges();
}
When calling Add on a DBSet with a model that was not loaded from EF, it thinks it is untracked and will always assume it is new.
Instead, you need to load the existing record from the dbcontext and map the properties from the data passed into the API to the existing record. Typically that is a manual map from parameter object to domain. Then if you return an object back, you would map that new domain object to a DTO. You can use services like AutoMapper to map the domain to a DTO. When you're done mapping, you only need to call SaveChanges.
Generally speaking, loading the record and mapping the fields is a good thing for the security of your API. You wouldn't want to assume that the passed in data is pristine and honest. When you give the calling code access to all the properties of the entity, you may not be expecting them to change all the fields, and some of those fields could be sensitive.

this[propertyName] is not a function in breeze.debug.js

I am using Hot towel template and extended functionality of it by using breeze. I have used breeze.partial-entities.js file to conver breeze entities to proper dtos that can be used by knockout observables as shown below.
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
// We don't have it, so create it as a partial
extendWith = $.extend({ }, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
For few of the entities it is working properly and getting breeze data converted to entities but for one of the entity implementation is failing. Model for the same is given as below.
public class StandardResourceProperty
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int StandardResourceId{ get; set; }
public int InputTypeId{ get; set; }
public int ListGroupId{ get; set; }
public string Format{ get; set; }
public string Calculation{ get; set; }
public bool Required{ get; set; }
public int MinSize{ get; set; }
public int MaxSize{ get; set; }
public string DefaultValue{ get; set; }
public string Comment { get; set; }
public virtual StandardResource AssociatedStandardResource { get; set; }
public virtual List AssociatedList { get; set; }
}
The error i am getting is
TypeError: this[propertyName] is not a function
[Break On This Error]
thispropertyName;
breeze.debug.js (line 13157)
]
with code
proto.setProperty = function(propertyName, value) {
this[propertyName](value);
// allow set property chaining.
return this;
};
Please let me know . What can be possible issue with the implementation also , it would be great if i can get more suggestion on how to debug and trace such issues.
Let's back up. I do not understand what you mean by "convert breeze entities to proper dtos that can be used by knockout observables". Breeze entities are already configured as KO observables (assuming you are using the default Breeze model library configuration). What are you trying to do?
I suspect you are following along with the Code Camper Jumpstart course where it does a getSessionPartials projection query. That query (like all projections) returns DTOs - not entities - and maps them with the dtoToEntityMapper method into Session entities.
The CCJS dtoToEntityMapper method cannot be used with entities. It is for converting from a DTO to an Entity and takes DTOs - not entities - as input.
Goodbye to dtoEntityMapper
The dtoToEntityMapper method pre-dates the ability of Breeze to automate projection-to-entity mapping by adding .toType('StandardResourceProperty') to your projection query.
Here is what the CCJS getSessionPartials query could look like now:
var query = EntityQuery
.from('Sessions')
.select('id, title, code, speakerId, trackId, timeSlotId, roomId, level, tags')
.orderBy(orderBy.session)
.toType('Session');
If you go this way, be sure to set the default state of the isPartial flag to true in the custom constructor (see model.js)
metadataStore.registerEntityTypeCtor(
'Session', function () { this.isPartial = true; }, sessionInitializer);
Note that this.isPartial = true is the reverse of the CCJS example where the default was false.
Make sure that you set isPartial(false) when you query or create a full entity. In CCJS there are two places to do that: in the success-callback of getSessionById AND in createSession which would become:
var createSession = function () {
return manager.createEntity(entityNames.session, {isPartial: false});
};

Entity Framework 5 Cyclic Lazy Loading Cause OutOfMemoryException

I have problem with EF 5 and Lazy loading with cyclic references.
The below picture represents my model.
The main problem is between Model and ModelProperties classes, because Model contains IEnumerable navigation property and ModelProperty contains Model navigation property.
So this design cause the situation below
You can access fullsize image http://tinypic.com/r/2vskuxl/6
As you can imagine this cause very big problem, OutOfMemory exception.
Only solution that i could find is disabling lazy loading and using other methods. But lazy loading is very simplifying our work.
I hope there is a configuration or an attribute to help me loading only two levels of relations with lazy loading.
Is there any way to achieve this?
UPDATE:
Regarding the request from Julie Lerman, here is the visual model of EF.
I highlighted the main relation that cause problem.
Also you can access full-size at http://tinypic.com/r/30v15pg/6
UPDATE 2:
Here is the model definitions.
public class Model {
public int ModelID { get; set; }
public int BrandID {
get;
set;
}
public virtual Brand Brand { get; set; }
public string Logo { get; set; }
public string Name { get; set; }
public virtual ICollection<ModelProperty> ModelProperties {
get;
set;
}
}
public class ModelProperty {
public int ModelPropertyID {
get;
set;
}
public virtual int PropertyDefinitionID {
get;
set;
}
public virtual PropertyDefinition PropertyDefinition {
get;
set;
}
public virtual int ModelID {
get;
set;
}
public virtual Model Model {
get;
set;
}
public bool IsContainable {
get;
set;
}
public bool HasFilterDefinition {
get;
set;
}
public virtual ICollection<ModelPropertyValue> ModelPropertyValues {
get;
set;
}
public virtual ICollection<ModelPropertyMatchingFilter> ModelPropertyMatchingFilter {
get;
set;
}
}
Also there is an entity configuration for ModelProperty.
public class ModelPropertyEntityTypeConfiguration : EntityTypeConfiguration<ModelProperty> {
public ModelPropertyEntityTypeConfiguration() {
HasKey(p => p.ModelPropertyID);
HasRequired(p => p.PropertyDefinition).WithMany(s => s.ModelProperties).HasForeignKey(s => s.PropertyDefinitionID).WillCascadeOnDelete(false);
HasRequired(p => p.Model).WithMany(s => s.ModelProperties).HasForeignKey(s => s.ModelID).WillCascadeOnDelete(false);
HasMany(p => p.ModelPropertyValues).WithRequired(s => s.ModelProperty).HasForeignKey(s => s.ModelPropertyID).WillCascadeOnDelete(true);
HasMany(p => p.ModelPropertyMatchingFilter).WithRequired(s => s.ContainerModelProperty).HasForeignKey(s => s.ContainerModelPropertyID).WillCascadeOnDelete(false);
ToTable("dbo.ModelProperties");
}
}
UPDATE 3:
I am not sure but Automapper can cause this also. Because Entity Framework Profile tells thousands of Autommaper methods called while running.
UPDATE 4:
Here is the EFProf stacktrace:
You access bigger version http://tinypic.com/r/21cazv4/6
UPDATE 5
You can see sample project here: https://github.com/bahadirarslan/AutomapperCircularReference
In sample, you can see easily endless loops via Quick watch.
Thanks for the update. You're model looks fine. It definitely knows this is just a 1:* relationship. Ladislav (as usual) is correct. LL doesn't cause the problem...except in ONE place which is during serialization. Is there a chance that you have your code in a service? With regular lazy loading, only the property you explicitly mention will get lazy loaded. But during serialization, the serialization "mentions" every property so it just keeps loading and loading properties throughout the graph AND causes circular dependency issues. With services we have to turn lazy loading off (using context.configuration.lazyloadingenabled=false) before you return the data. So in the service method, you can eager load, or lazy load or explicitly load to get your graph but then disable lazy loading before you return the results.
you should disable lazy loading in order to bypass proxy objects or you should return what you need as a DTO. using DTO is preferred because of hiding details of your domain

Automapper and NHibernate lazy loading

I am struggling with this issue:
I have a list of NHibernate objects called "Project". These objects contain a lazy - loaded list of "Branches". I am trying to pass a list of Projects to a WCF service so I am using AutoMapper to transform them to flat objects.
The problem is that even though the destination objects called "ProjectContract" does not contain a list of Branches, Automapper still invokes this collection and a lot of queries are made to the database because NHibernate fires the lazy - loading and loads the Branches collection for each project.
Here are the classes and the mapping:
public class Project
{
public virtual int ID
{
get;
set;
}
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual IList<Branch> Branches { get; set; }
}
[DataContract]
public class ProjectContract
{
[DataMember]
public virtual int ID
{
get;
set;
}
[DataMember]
public virtual string Name { get; set; }
[DataMember]
public virtual string Description { get; set; }
}
public class ProjectMappings : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Project, ProjectContract>();
}
}
My question is: Is there a way to tell AutoMapper to not touch the "Branches" collection because I don't care about it and that is a proxy that will trigger many database calls?
I temporarily fixed this with MaxDepth(0), but there are other entities where I have collections that I want to transfer, and collections that I don't want to be touched, like this one. In that case, MaxDepth(0) will not work.
Thank you,
Cosmin
Yes, The AutoMapper Ignore function.
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());