EF7 How to handle the update operation for nested entities - c#

I am trying to figure out the best practices when updating an entity and all it's children. For example; I have an "Employer" update service that would update the employer entity and the "Address" entities of the employer and the "Phone" entities of each "Address". User may add new addresses to the existing employer or they may update the current addresses or they can delete some, same applies for the phones of each addresses. Could you help me to write the ideal code that would handle this scenario.
I am using EF7 rc1 and I use Automapper to map the Dto to Entity in my service.
public partial class Employer
{
public int EmployerId { get; set; }
public int Name { get; set; }
[InverseProperty("Employer")]
public virtual ICollection<Address> Address { get; set; }
}
public partial class Address
{
public int AddressId { get; set; }
public int Address1{ get; set; }
public int City { get; set; }
[ForeignKey("EmployerId")]
[InverseProperty("Address")]
public virtual Employer Employer { get; set; }
[InverseProperty("Address")]
public virtual ICollection<Phone> Phone { get; set; }
}
public partial class Phone
{
public int PhoneId { get; set; }
public string Number { get; set; }
[ForeignKey("AddressId")]
[InverseProperty("Phone")]
public virtual Address Address { get; set; }
}
My service method;
public async Task<IServiceResult> Update(EmployerDto employer)
{
var employerDbEntity = await _db.Employer
.Include(a=>a.Address).ThenInclude(p=>p.Phone)
.SingleOrDefaultAsync (a=>a.EmployerId == employer.EmployerId);
//How to handle the update operation for children?
var entity = Mapper.Map<Employer>(employer);
HandleChildren(employerDbEntity,entity);
await _db.SaveChangesAsync();
...
...
}
private void HandleChildren(Employer employerDbEntity,Employer entity)
{
//Delete
foreach (var existing in employerDbEntity.Address.ToList())
{
if (!entity.Address.Any(a => a.AddressId == existing.AddressId))
employerDbEntity.Address.Remove(existing);
}
//Update or Insert
foreach (var address in entity.Address)
{
var existing = employerDbEntity.Address.SingleOrDefault(a =>a.AddressId == address.AddressId);
//Insert
if (existing == null)
{
employerDbEntity.Address.Add(address);
}
//Update
else
{
Mapper.Map(address, existing);
}
}
}

This example looks like a decent way to handle child collections. Each collection has to be checked manually for the action performed. (Using generics sounds good, but always bites back in some way. Typically, performance.)
With that in mind, here's a few recommendations:
Move the child collection handling into separate methods/services.
If querying for existing entities, retrieve the whole collection in one query then iterate the results in memory.
Since you're writing async code, you can take advantage of processing the child collections in parallel! To do this, each operation should create it's own context. This explains why it's faster.
Here's an example using the recommendations:
private async Task UpdateAddresses(List<Address> addressesToUpdate)
{
using(var context = new Context())
{
var existingAddressIds = await context.Addresses
.Where(a => addressesToUpdate.Contains(a.AddressId))
.ToListAsync()
.ConfigureAwait(false);
existingAddressIds.ForEach(a => context.Addresses.Remove(a));
await context.SaveChangesAsync().ConfigureAwait(false);
}
}

Related

A possible object cycle was detected. This can be due to a cycle or if the object depth is larger than the maximum allowed depth of 32

I have encountered really weird situation. I am developing a net 5 api, and (among ohther entities) have three tables, Doctor, Specialization, and DoctorSpecialization. My entities:
public class Doctor
{
public int Id { get; set; }
public string Name { get; set; }
public string Resume { get; set; }
public ICollection<DoctorSpecialization1> DoctorSpecializations { get; set; }
}
public class DoctorSpecialization
{
public int Id { get; set; }
public int Doctor1Id { get; set; }
[ForeignKey("Doctor1Id")]
public Doctor1 Doctor { get; set; }
public int Specialization1Id { get; set; }
[ForeignKey("Specialization1Id")]
public Specialization1 Specialization { get; set; }
}
public class Specialization
{
public int Id { get; set; }
public string SpecializationName { get; set; }
}
I want to fetch all the specializations associated with the certain doctor, therefore I created a service:
public class DoctorService : IDoctorService
{
public async Task<List<Specialization>> GetAllSpecializationsForDoctor(int id)
{
var doctor = await _context.Doctors.Where(x => x.Id == id).FirstOrDefaultAsync();
var doctorSpecializations = await _context.DoctorSpecializations.
Where(x => x.DoctorId == doctor.Id)
.ToListAsync();
IEnumerable<int> ids = doctorSpecializations.Select(x => x.SpecializationId);
var specializations = await _context.Specializations.Where(x =>
ids.Contains(x.Id)).ToListAsync();
return specializations;
}
}
In the end, I am adding a method in my controller which is supposed to fetch specializations based on doctor's id:
[HttpGet("specializations/{id}")]
public async Task<ActionResult<List<Specialization1>>> GetSpecializationsForDoctor(int id)
{
var doctor = await _doctorService.FindDoctorById(id);
if (doctor == null) return NotFound();
var specialization = _doctorService.GetAllSpecializationsForDoctor(id);
return Ok(specialization);
}
When I run this in postman, I get the error stated in the title of this question. However, I encountered an article that explains I should install newtonsoft.json and make some changes in my Startup in order to overcome this issue. So therefore I installed Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.10" and made changes in my Starup as follows:
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore);
This time I get the expected result, but my postman shows tons of data before giving me the wanted result, and I wonder is it ok to return this much data to the client. Can somebody explain me what is going on, tnx in advance!
Apparently the solution was so simple, I missed the await in:
var specialization = await
_doctorService.GetAllSpecializationsForDoctor(id);
Once again I managed to find solution thanks to stackoverflow, because apparently someone else had this problem in: JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than
So please everyone, don't be so superficial as I was, make sure to use await and save your time. Thanks again to the community:).

LazyLoading not behaving as expected in EFCore

Here are 2 (minimized classes):
[Index(nameof(Name), IsUnique = true)]
public partial class ParticipantList
{
public ParticipantList()
{
Emails = new HashSet<Email>();
}
[Key]
[Column("id")]
public long Id { get; set; }
//...
[InverseProperty(nameof(Email.ParticipantList))]
public virtual ICollection<Email> Emails { get ; set; }
}
public partial class Email
{
[Key]
[Column("id", TypeName = "integer")]
public long Id { get; set; }
[Column("participant_list_id", TypeName = "integer")]
public long? ParticipantListId { get; set; }
/...
[ForeignKey(nameof(ParticipantListId))]
[InverseProperty("Emails")]
public virtual ParticipantList ParticipantList { get; set; }
}
And the DbContext OnConfiguring method contains:
optionsBuilder
.UseLazyLoadingProxies()
.UseSqlite(...);
And the DBContext OnModelCreating method contains:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Email>(entity =>
{
entity.HasOne(d => d.ParticipantList)
.WithMany(p => p.Emails)
.HasForeignKey(d => d.ParticipantListId)
.OnDelete(DeleteBehavior.ClientSetNull);
});
This is what works:
var email = _db.Emails.Find(1);
// email.ParticipantList (this lazy loads just fine).
What doesn't work:
var plist = _db.ParticipantLists.Find(1);
// plist.Emails.Count() == 0 (this doesn't lazy load at all)
// plist.Emails.Count == 0 (again no lazy loading)
// plist.Emails.ToList() (empty list, no lazy loading)
I should insert a rant here about the stupidity of having both Count() and Count with different meanings here. I mean, seriously, who is going to catch that during a code review????
This is most likely due to:
public ParticipantList()
{
Emails = new HashSet<Email>();
}
Which was created by the ef-scaffold app. I mean it makes sense, the HashSet is empty, but it seems like the model should override the access to the HashSet and do the lazy loading.
The problem is that if you remove this:
public ParticipantList()
{
// Emails = new HashSet<Email>();
}
And then call this code:
// plist.Emails.Count() == 0 (Now you get a null reference exception
// because guess what: Emails is not initialized!)
// plist.Emails.ToList() (Also gives a null reference exception)
// plist.Emails.Count == 0 (Again, a null reference exception)
The documentation shows code like this (note that Posts is NOT initialized in the example at https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy ):
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual Blog Blog { get; set; }
}
Which will purportedly lazy load the Posts from the Blog. But when I follow that pattern and remove the ctor that initializes Emails/Posts I get a null reference exception.
To add to the mystery/madness is this:
var email = _db.Emails.Find(1);
var plist = _db.ParticipantLists.Find(1);
// plist.Emails.Count() == 1 (It knows about the relation and added it to
// the Emails HashSet on the ParticipantList!)
So, how do I get Emails to LazyLoad. I know about the 1 + N problem, and I'm not worried about that aspect, but I'd like the models to load when they are requested.

Copying data between models and saving children without entities duplicating themselves in Entity Framework

I am having trouble saving children entities via Entity Framework / ASP Identity. It seems to be adding duplicates of everything that is added.
I have tried using a detached graph of the DrivingLicenceModel by TeamMember.DrivingLicence = null in the TeamMemberModel and then working with a detached graph by looking if there is new or old DrivingLicenceCategories but because DrivingLicence links back to TeamMember it causes TeamMember.DrivingLicenceId to be null as it cannot link back to TeamMember.
I have tried Manually adding the EntityState to the DrivingLicence and DrivingLicenceCategories but when I do that it complains that it cannot save two entities with the same primary key.
I assume this is because they way I am copying the entities but I after a lot of looking I am drawing a blank.
If there anyway to copy from TeamMemberRequestModel to TeamMemberModel and then save without the children trying to create clone copies of themselves?
Models
public class TeamMemberModel : IdentityUser
{
public virtual DrivingLicenceModel DrivingLicence { get; set; }
public void ShallowCopy(TeamMemberRequestModel src)
{
this.DateOfBirth = src.DateOfBirth;
if (src.DrivingLicence != null)
{
if (this.DrivingLicence == null)
{
this.DrivingLicence = new DrivingLicenceModel(src.DrivingLicence);
}
else
{
this.DrivingLicence.ShallowCopy(src.DrivingLicence);
}
}
}
public TeamMemberModel() { }
}
public class DrivingLicenceModel
{
[Key]
public int Id { get; set; }
[ForeignKey("TeamMember")]
public string TeamMemberId { get; set; }
[JsonIgnore]
public TeamMemberModel TeamMember { get; set; }
public virtual List<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
public DrivingLicenceModel() { }
public DrivingLicenceModel(DrivingLicenceModel src)
{
this.ShallowCopy(src);
}
public void ShallowCopy(DrivingLicenceModel src)
{
this.Id = src.Id;
this.IsFullLicence = src.IsFullLicence;
this.IssueDate = src.IssueDate;
this.ExpiryDate = src.ExpiryDate;
this.IssuingAuthority = src.IssuingAuthority;
this.LicenceNumber = src.LicenceNumber;
this.DrivingLicenceCategories = src.DrivingLicenceCategories;
this.DrivingLicencePoints = src.DrivingLicencePoints;
}
}
public class DrivingLicenceCategoryModel
{
[Key]
public int Id { get; set; }
[ForeignKey("DrivingLicence")]
public int DrivingLicenceId { get; set; }
[JsonIgnore]
public DrivingLicenceModel DrivingLicence { get; set; }
}
public class TeamMemberRequestModel
{
public string Id { get; set; }
public virtual DrivingLicenceModel DrivingLicence { get; set; }
}
Context
public class TIERDBContext : IdentityDbContext<TeamMemberModel, RoleModel, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public TIERDBContext() : base("SARDBConnection") { }
public DbSet<DrivingLicenceModel> DrivingLicences { get; set; }
public DbSet<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
}
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TeamMemberModel CurrentTeamMember = await this.TIERUserManager.FindByIdAsync(id);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
you have to create clone property into context class
.
In the context clases you could to use clone method that retiran the entity you send by parameters this duplicarse any entity you pass. Sorry for my english
hope you help
After far to many hours working over this. I have come to an answer. The best way to deal with this is to simply deal with it is to add or attach all entities down the tree.
The controller now attaches all children unless they have an ID of 0, therefore new and uses add instead. Then I use this very useful extension I found here http://yassershaikh.com/c-exceptby-extension-method/ to compare lists to see added and deleted entities in the list. While I don't need the added part as the entity will already be marked to an add state as I use add() it does not harm and I want to use it later with add and delete state changing.
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TIERDBContext IdentityContext = (TIERDBContext)this.TIERUserManager.UserStore().Context;
foreach (DrivingLicenceCategoryModel DrivingLicenceCategory in teamMember.DrivingLicence.DrivingLicenceCategories)
{
if (DrivingLicenceCategory.Id == 0)
{
IdentityContext.DrivingLicenceCategories.Add(DrivingLicenceCategory);
}
else
{
IdentityContext.DrivingLicenceCategories.Attach(DrivingLicenceCategory);
}
}
foreach (DrivingLicencePointModel DrivingLicencePoint in teamMember.DrivingLicence.DrivingLicencePoints)
{
if (DrivingLicencePoint.Id == 0)
{
IdentityContext.DrivingLicencePoints.Add(DrivingLicencePoint);
}
else
{
IdentityContext.DrivingLicencePoints.Attach(DrivingLicencePoint);
}
}
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicenceCategories.AsQueryable(),teamMember.DrivingLicence.DrivingLicenceCategories, IdentityContext);
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicencePoints.AsQueryable(),teamMember.DrivingLicence.DrivingLicencePoints, IdentityContext);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
I then use a generic that uses ExceptBy to work out what is added and delete from the old team member model to the new team member model.
protected void DetectAddedOrRemoveAndSetEntityState<T>(IQueryable<T> old, List<T> current, TIERDBContext context) where T : class, IHasIntID
{
List<T> OldList = old.ToList();
List<T> Added = current.ExceptBy(OldList, x => x.Id).ToList();
List<T> Deleted = OldList.ExceptBy(current, x => x.Id).ToList();
Added.ForEach(x => context.Entry(x).State = EntityState.Added);
Deleted.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
It works but it is far from great. It takes two DB queries, getting the original and updating. I just cannot think of any better way to do this.

Combine workings of Query / QueryOver and DetachedCriteria in NHibernate

In my current project, I started using NHibernate and basically everything works nicely, up to a point, where I need to send complete object tree for some entity to the consumer of my service = everything eagerly loaded and sent from the server.
And I want my queries to be as fast as possible.
The search criteria is rather simple: the complete newest result object for the specified product and the specified department.
If i call my query like this, I am getting the correct result:
(however this produces 3 queries, still, at least I am getting the correct result)
Result res = session.QueryOver<Result>()
.Where(x => x.Product == product)
.Where(x => x.Department == department)
.OrderBy(x => x.Date).Desc
.Take(1).SingleOrDefault();
However, such result is not eagerly loaded with all the child collections.
(and yes, it still creates 3 queries).
For I definitelly need all the items of type ResultContent in Content populated. ResultContent has some collections of elements, that have their collections as well.
I desperately need all those collections fully populated with all the child elements and child elements of its child elements and so on.
I played with NHibernate queries nearly whole day now and am nearly achieving correct behavior with query like this:
var resultCriteria = DetachedCriteria.For<Result>();
resultCriteria.SetFetchMode("Product", FetchMode.Eager);
resultCriteria.SetFetchMode("Department", FetchMode.Eager);
resultCriteria.SetFetchMode("Content", FetchMode.Join);
resultCriteria.SetFetchMode("Source", FetchMode.Join);
resultCriteria.SetFetchMode("Content.Numerics", FetchMode.Eager);
resultCriteria.SetFetchMode("Content.Numerics.DataCollection", FetchMode.Join);
resultCriteria.SetFetchMode("Content.Numerics.DataDescription", FetchMode.Join);
resultCriteria.SetFetchMode("Content.Numerics.DataDescription.DescriptionSource", FetchMode.Eager);
resultCriteria.SetMaxResults(5);
var relevantResults = resultCriteria.GetExecutableCriteria(session).List<Result>();
With this approach, I am getting 5 Results, but the Content collection is not populated further than the first element. And for the DataCollection in Numerics, there only are 5 elements queried, even though it should be somewhere near 500 items pulled.
I Played with the FetchModes quite a lot and am starting to think that I will not achieve what I need nowhere near in the future.
Could you please hint me in the proper direction how I could combine those 2 approaches properly?
Should I use HQL (I never did, so I think I would struggle even more)?
Note: I googled quite a lot and tried a lot of things, but as far as NHibernate and sql goes, I consider myself quite a noob. I will try improving my skills, but the time pressure now tends towards asking a question.
The other relevant classes look like this (so that you can see which class holds a collection of what, lots of simplifications applied of course)
public class Result
{
public virtual long Id { get; protected set; }
public virtual Product Product { get; set; }
public virtual Department Department { get; set; }
public virtual DateTime Date { get; set; }
public virtual ISet<ResultContent> Content { // getters and setters }
public virtual ISet<ResultSource> Source { // getters and setters }
}
public class Product
{
public virtual short Id { get; protected set; }
public virtual string Name { get; set; }
public virtual string Code { get; set; }
public virtual string Description { get; set; }
}
public class Department
{
public virtual short Id { get; protected set; }
public virtual string Name { get; set; }
public virtual string Code { get; set; }
public virtual string Description { get; set; }
}
public class ResultContent
{
public virtual Result Result { get; set; }
public virtual Numerics Numerics { get; set; }
public virtual long Revision { get; set; }
}
public class Numerics
{
public virtual long Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ISet<DataEntry> DataCollection { //getters and setters }
public virtual ISet<DataDescription> DataDescription { //getters and setters}
}
DataEntry and DataDescription Classes are just holding Id, Date and either string or boolean value.
hi can you try it ,
Result ra = null;
Product pa = null;
Department da = null;
Result res = session.QueryOver<Result>(() => ra)
.JoinAlias(() => ra.Product ,() => pa)
.JoinAlias(() => ra.Department ,() => da)
.Where(() => pa == product)
.Where(() => da == department)
.OrderBy(() => ra.Date).Desc
.Take(1).SingleOrDefault();

most efficient way to populate composite class

I just want to get some opinions on the most efficient and quickest way to populate my composite class using the EF4.0. I have a parent class which has a structure similar to the class below. It mirrors my database structure.
public class Person
{
public string FirstName { get; set; }
........
public Address WorkAddress { get; set; }
public IList<Account> Workspace { get; set; }
}
public class Address
{
public string FirstName { get; set; }
......
}
public class Account
{
public string SortCode { get; set; }
public IList<string> TransactionHistory {get; set;}
......
}
So, at this moment in time I pull back all the 'Persons' from the EF and I loop through each of them and populate the Address and Accounts for each. Lazy loading is enabled, so I have to encapsulate all my loops in the using statement or my Accounts will be empty when I try to iterator through them. So, can I disable lazy loading for this call only or should approach the population of my list of persons in another manner.
using (var entities = new PersonEntities())
{
var dbPeople = (from person in entities.Persons
select person).ToList();
foreach(var person in dbPeople)
{
foreach(var account in person.Accounts)
{
// In here I populate my 'Person' business object account and add it to my collection to return.
}
}
}
If I got you right, you're going to include the relation keeping the LazyLoading enabled. You can do this for the query using the Include method:
using (var entities = new PersonEntities())
{
var dbPeople = entities.Persons.Include("Accounts").ToList();
foreach(var person in dbPeople)
{
//Do nothing with Accounts if the relation is mapped correct
}
}
EDIT:
Also you can disable LazyLoading for the created PersonEntities instance's lifetime:
using (var entities = new PersonEntities())
{
entities.Configuration.LazyLoadingEnabled = false;
//...
}

Categories