EF Core 5 One-to-many relationship problem - c#

In this example a user has zero or many bills, one bill can be assigned to one user. Bill can also be created but never assigned.
public class User
{
public int Id{ get; set; }
public List<Bill> bills{ get; set; }
}
public class Bill
{
public int Id { get; set; }
public int userId{ get; set; }
public User user{ get; set; }
}
I've also added this in my DB context configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Bill>()
.HasOne(b => b.user)
.WithMany(u => u.bills)
.HasForeignKey(b => b.userId);
}
I've realized it through a unit of work + repository pattern. In my BillService.cs I would like to have a method that allows me to update/add a bill and assign it to a user.
If the user doesn't exist in DB it should add it. If the user exists it should update it.
I've tried two approaches.
First:
public async Task<void> AddUpdateBill(AddBillModel model){
Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
if( unitOfWork.UserRepo.GetById(model.userId) == null){
unitOfWork.UserRepo.Insert(model.user);
}else{
unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;
unitOfWork.BillRepository.Update(bill);
unitOfWork.Save();
}
Second:
public async Task<void> AddUpdateBill(AddBillModel model)
{
Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
bill.user = model.user;
unitOfWork.BillRepository.Update(bill);
unitOfWork.Save();
}
In both cases, I've got the problem of duplicated primary-key or entity already tracked.
Which is the best approach or the right way to do it?
EDIT: Sorry, BillRepo and BillRepository are the same class.
public async Task<Bill> GetByID(int id)
{
return await context
.bill
.Include(b => b.user)
.Where(b=> b.id == id)
.FirstOrDefaultAsync();
}
public void Update(Bill bill)
{
context.Entry(bill).CurrentValues.SetValues(bill);
}

The first approach seems more right (to me).
First of all, comply with the naming rules: all properties must begin with upper case characters. "Bills", "UserId", "User" in your case.
if( unitOfWork.UserRepo.GetById(model.userId) == null){
unitOfWork.UserRepo.Insert(model.user);
}else{
unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;
You don't need it here
bill.user = model.user;
because you have just attached your entity to context and updated/inserted it.
Also, don't forget to format your code, for example https://learn.microsoft.com/ru-ru/dotnet/csharp/programming-guide/inside-a-program/coding-conventions
It would be useful to consider inserting/updating your entities not straight from the model, something like:
if( unitOfWork.UserRepo.GetById(model.userId) == null){
var user = new User
{
//set properties
};
unitOfWork.UserRepo.Insert(user);
unitOfWork.Save();
bill.userId = user.Id;
}

Here:
if( unitOfWork.UserRepo.GetById(model.userId) == null){...
you retrieve the User from UserRepo but don't assign it to any variable. This may cause the exception stating that there are multiple tracked entities with the same ID.
Try to retrieve (including bills) or create the User entity and add the new bill in there. Then insert User entity to DB (if it was not there) and simply Save your work.

Related

EF core ignore navigational property on save

It is just an illustratory example, I understand the relations in this example do not make sense perse, but it plots relations in a way I need the solution. So please do not comment about that.
I am searching for a solution in which I can ignore saving a navigational property;
public class ClassRoom {
public Guid Id { get; set; }
public Guid? ClassRoomInformationId { get; set; }
public virtual ClassRoomInformation { get; set; }
public virtual Collection<Student> Students { get; set;
}
public class Student {
public Guid Id { get; set; }
public Guid? ClassRoomId { get; set; }
public Guid? StudentInformationId { get; set; }
public virtual StudentInformation { get; set; }
}
public class StudentEntityConfiguration : IEntityTypeConfiguration<Student> {
public void Configure(EntityTypeBuilder<Student> builder) {
builder.ToTable("Student");
builder.HasKey(s => s.Id);
builder.HasOne(s => s.StudentInformation)
.WithOne()
.HasForeignKey<Student>(s => s.StudentInformationId);
}
}
public class ClassRoomEntityConfiguration : IEntityTypeConfiguration<ClassRoom> {
public void Configure(EntityTypeBuilder<ClassRoom> builder) {
builder.ToTable("ClassRoom");
builder.HasKey(c => c.Id);
builder.HasOne(c => c.ClassRoomInformation)
.WithOne()
.HasForeignKey<ClassRoom>(c => c.ClassRoomInformationId);
builder.HasMany(c => c.Students)
.WithOne()
.HasForeignKey(c => c.ClassRoomInformation);
}
}
To clearify my question (Using EF 2.2); I want to update the student through it's own StudentRepository. And when I save a classroom through the ClassRoomRepository and the student might change in any way, I do not want that change to be persisted (even though it is included to be able to 'view' the data).
I have tried to add the following to the ClassRoomEntityConfiguration:
//BeforeSaveBehavior neither works
builder.Property(c => c.Students).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
However this gives the following exception:
... Cannot be used as a property on ... because it is configured as a navigation.'
Another thing I tried is setting the componentmodel readonly attribute on the Students list in the ClassRoom. This seems to be ignored as well.
I call this the goldilocks problem. You have a hierarchy of objects (Customer, Order, OrderDetails) and you only want to save at "just the right level" of the object-graph.
A work around is to load the object......change only thing things that you care about, then save it.
In the below, I am NOT saving the inputItem.
I am using inputItem to set a small subset of the values of the foundEntity.
public async Task<MyThing> UpdateAsync(MyThing inputItem, CancellationToken token)
{
int saveChangesAsyncValue = 0;
MyThing foundEntity = await this.entityDbContext.MyThings.FirstOrDefaultAsync(item => item.MySurrogateKey == inputItem.MySurrogateKey, token);
if (null != foundEntity)
{
/* alter JUST the things i want to update */
foundEntity.MyStringPropertyOne = inputItem.MyStringPropertyOne;
foundEntity.MyStringPropertyTwo = inputItem.MyStringPropertyTwo;
this.entityDbContext.Entry(foundEntity).State = EntityState.Modified;
saveChangesAsyncValue = await this.entityDbContext.SaveChangesAsync(token);
/* an exception here would suggest another process changed the "context" but did not commit the changes (usually by SaveChanges() or SaveChangesAsync() */
if (1 != saveChangesAsyncValue)
{
throw new ArgumentOutOfRangeException(string.Format("The expected count was off. Did something else change the dbcontext and not save it? {0}", saveChangesAsyncValue), (Exception)null);
}
}
else
{
ArgumentOutOfRangeException argEx = new ArgumentOutOfRangeException(string.Format(" SAD FACE {0} ", entity.MyThingKey), (Exception)null);
this.logger.LogError(argEx);
throw argEx;
}
return foundEntity;
}
SIDE NOTE:
2.2 is no longer supported (see link below). Dot Net Core 2.2 End of Lifetime is listed as "December 23, 2019"
You should upgrade to 3.1 or downgrade to 2.1. (downgrading is counter intuitive I know).
See
https://dotnet.microsoft.com/platform/support/policy/dotnet-core

Do we have to explicitly add to db context?

public class Practice
{
public List<Participation> Participation { get; set; }
}
public class Participation
{
public string Id { get; set; }
public virtual Practice Practice { get; set; }
}
public void test()
{
var practice = _ctx.Practice.SingleOrDefault(p => p.Id == practiceId);
practice.Participations.AddRange(NewParticipations);
_ctx.Participation.AddRange(NewParticipations)
await _ctx.SaveChangesAsync();
}
If I have the above, would I need the 3rd line in the test function to save new participations or would the practice.Participations.AddRange() handle that implicitly?
practice.Participations.AddRange should be enough.
If you reference a new entity from the navigation property of an entity that is already tracked by the context, the entity will be discovered and inserted into the database.
source: https://learn.microsoft.com/en-us/ef/core/saving/related-data#adding-a-related-entity
You can observe it like so...
var practice = _ctx.Practice.SingleOrDefault(p => p.Id == practiceId);
practice.Participations.AddRange(NewParticipations);
Debug.WriteLine(_ctx.Participation.Count()); //note count
await _ctx.SaveChangesAsync();
Debug.WriteLine(_ctx.Participation.Count()); //count increased
You should be able to add the new data to the database either way. If you added through the context, you would need to set the foreign key in the NewParticipations objects yourself, so that a link would exist to the Practice object.

Update an existing entry with Entity Framework

I want to update an existing entry with entity framework and here is my current code:
[HttpPost]
public IActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
var result = _productRepository.Query().FirstOrDefault(x => x.Id == product.Id);
if (result == null)
return RedirectToAction("Index", "Products");
_productRepository.Update(product);
//result = product;
_productRepository.Save();
return View("Edit", result);
}
What have I tried:
result = product; doesn't seem to update the row in db.
public void Update(T item)
{
_context.Entry(item).CurrentValues.SetValues(item);
}
doesn't seem to update the row in db.
result.Title = product.Title - works, but then I have to do this for each field, is there a way to update a row simply by replacing the values with another object?
Edit
Actually, I realized that the code below will not work because you are already tracking the same entity, this is caused by this line:
var result = _productRepository.Query().FirstOrDefault(x => x.Id == product.Id);
So you need to either remove that line and just use the Update method below with the product object as the parameter or use the result object and update it based on the data from your product class and then save:
result.Name = product.Name;
[...]
In this case you don't need to call _repository.update, just _repository.save
Using product to update
Assuming your Product class is an object of the same class as your Product entity class you need to make sure that it is being tracked by entity framework and to mark it as modified before it can be saved:
To do that, modify your update method as follows:
public void Update(T item)
{
if (!_context.Set<T>().Local.Any(e => e == item))
{
_context.Set<T>().Attach(item);
}
_context.Entry(item).State = EntityState.Modified
}
Then just save it and it should work:
_productRepository.Update(product);
_productRepository.Save();
A better approach?
Instead of sending entity framework entities back and forth to and from your views, you could create a model class specifically for your view and then retrieve and update your database entity models as needed:
For example if your Product database entity looks like this:
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public int InternalId { get; set; }
}
In your view you don't need / want to use the InternalId field, so you would have a model in your Website assembly that could look like the following:
public class ProductModel
{
public int Id { get; set; }
public string ProductName { get; set; }
}
And then in your controller, this is what you will use:
[HttpPost]
public IActionResult Edit(ProductModel product)
{
if (!ModelState.IsValid)
{
return View(product);
}
var dbProduct = _productRepository.Query().FirstOrDefault(x => x.Id == product.Id);
if (dbProduct == null)
{
//Product doesn't exist, create one, show an error page etc...
//In this case we go back to index
return RedirectToAction("Index", "Products");
}
//Now update the dbProduct using the data from your model
dbProduct.ProductName = product.ProductName;
If you have a lot of fields, you don't want to do this manually, there are some libraries that will do this for you, for example, AutoMapper or my personal favorite (faster, easier to use) ValueInjecter
Using ValueInjecter you would do something like this to assign all the common properties automatically
dbProduct.InjectFrom(product);
Finally, just call save, this time you don't need to change the state because EF is already tracking your entity:
_productRepository.Save();

Entity Framework Delete Entity without its Dependency

While I am trying to delete my entity I am getting following error in my function;
A relationship from the 'ProjectWebsiteTag_ProjectUser' AssociationSet is
in the 'Deleted' state.
Given multiplicity constraints, a corresponding 'ProjectWebsiteTag_ProjectUser_Target' must also in the 'Deleted' state.
Here is my code to delete;
public bool Delete(int id)
{
try
{
using (ProjectDataContext context = new ProjectDataContext())
{
ProjectWebsiteTag websiteTag = context.WebsiteTags.FirstOrDefault(p => p.WebsiteTagId == id);
context.WebsiteTags.Remove(websiteTag);
int saveChanges = context.SaveChanges();
return saveChanges > 0;
}
}
catch (DbEntityValidationException e)
{
FormattedDbEntityValidationException newException = new FormattedDbEntityValidationException(e);
throw newException;
}
}
Here is my data class;
public class ProjectWebsiteTag
{
public int WebsiteTagId { get; set; }
public ProjectUser ProjectUser { get; set; }
public ProjectWebsite ProjectWebsite { get; set; }
}
My Config Class;
public ProjectWebsiteTagConfiguration()
{
ToTable("ProjectWebsiteTags");
HasKey(p => p.WebsiteTagId);
HasRequired(p => p.ProjectUser).WithRequiredDependent().WillCascadeOnDelete(false);
HasRequired(p => p.ProjectWebsite).WithRequiredDependent().WillCascadeOnDelete(false);
}
It looks like it is trying to delete User record, but I do not want that.
I just want to delete "ProjectWebsiteTag" and that's it.
What I am missing here?
It isn't trying to delete the ProjectUser. It's trying to insert it. ProjectWebsiteTagConfiguration() is saying that the ProjectWebsiteTags table has a ProjectUser foreign key in it. When you call
ProjectWebsiteTag websiteTag = context.WebsiteTags.FirstOrDefault(p => p.WebsiteTagId == id)
websiteTag has a ProjectUser with an empty string for its UserId property. So either the record in the ProjectWebsiteTags table has an empty string for the foreign key, or EF is newing a ProjectUser (with an empty UserId) when you get from the context. Either way, EF isn't aware of the existence of a ProjectUser with an empty string id, so when you call SaveChanges() it tries to add it. It can't because the UserId field is required and empty.
I had to fix my problem by adding one-to-many relationship configuration in my User class.
It comes to a resolution of I need to add relationship hint on both sides of Configuration declarations.
public class ProjectUserConfiguration : EntityTypeConfiguration<ProjectUser>
{
public ProjectUserConfiguration()
{
ToTable("ProjectUsers");
HasKey(p => p.UserId);
HasMany(p=>p.ProjectWebsiteTags).WithRequired(q=>q.ProjectUser).WillCascadeOnDelete(false);
}
}

Entity Framework 6 Update Graph

What is the correct way to save a graph of objects whose state you don't know? By state I mean whether they are new or existing database entries that are being updated.
For instance, if I have:
public class Person
{
public int Id { get; set; }
public int Name { get; set; }
public virtual ICollection<Automobile> Automobiles { get; set; }
}
public class Automobile
{
public int Id { get; set; }
public int Name { get; set; }
public short Seats { get; set; }
public virtual ICollection<MaintenanceRecord> MaintenanceRecords { get; set ;}
public virtual Person Person { get; set; }
}
public class MaintenanceRecord
{
public int Id { get; set; }
public int AutomobileId { get; set; }
public DateTime DatePerformed { get; set; }
public virtual Automobile Automobile{ get; set; }
}
I'm editing models, similar to these objects above, and then passing those models into the data layer to save, where for this instance I happen to be using entity framework. So I'm translating these models into POCO entities internal to the DAL.
It appears that unless my models have a state indicating whether they are new or updated, I have quite a bit of work to do to "Save" the changes. I have to first select the Person entity, update it, then match any existing Automobiles and update those and add any new, then for each automobile check for any new or updated maintenance records.
Is there a faster/easier way of doing this? It's possible I can keep track of the Model state, which I guess would be helpful with this, but it would mean changes to code outside of the data layer which i would prefer to avoid. I'm just hoping there is a pattern of usage out there that I can follow for updates like this.
I ran into this issue a while back and have been following this thread on the EF Codeplex site. https://entityframework.codeplex.com/workitem/864
Seems like it is being considered for the next release, I'm assuming EF 7, which apparently is a pretty large internal overhaul of EF. This may be worth checking out... http://www.nuget.org/packages/RefactorThis.GraphDiff/
Back when I was working on this I found another EF post on SO, and someone had an example of how to do this manually. At the time I decided to do it manually, not sure why, GraphDiff looks pretty cool. Here is an example of what I did.
public async Task<IHttpActionResult> PutAsync([FromBody] WellEntityModel model)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var kne = TheContext.Companies.First();
var entity = TheModelFactory.Create(model);
entity.DateUpdated = DateTime.Now;
var currentWell = TheContext.Wells.Find(model.Id);
// Update scalar/complex properties of parent
TheContext.Entry(currentWell).CurrentValues.SetValues(entity);
//We don't pass back the company so need to attached the associated company... this is done after mapping the values to ensure its not null.
currentWell.Company = kne;
// Updated geometry - ARGHHH NOOOOOO check on this once in a while for a fix from EF-Team https://entityframework.codeplex.com/workitem/864
var geometryItemsInDb = currentWell.Geometries.ToList();
foreach (var geometryInDb in geometryItemsInDb)
{
// Is the geometry item still there?
var geometry = entity.Geometries.SingleOrDefault(i => i.Id == geometryInDb.Id);
if (geometry != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(geometryInDb).CurrentValues.SetValues(geometry);
else
// No: Delete it
TheContext.WellGeometryItems.Remove(geometryInDb);
}
foreach (var geometry in entity.Geometries)
{
// Is the child NOT in DB?
if (geometryItemsInDb.All(i => i.Id != geometry.Id))
// Yes: Add it as a new child
currentWell.Geometries.Add(geometry);
}
// Update Surveys
var surveyPointsInDb = currentWell.SurveyPoints.ToList();
foreach (var surveyInDb in surveyPointsInDb)
{
// Is the geometry item still there?
var survey = entity.SurveyPoints.SingleOrDefault(i => i.Id == surveyInDb.Id);
if (survey != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(surveyInDb).CurrentValues.SetValues(survey);
else
// No: Delete it
TheContext.WellSurveyPoints.Remove(surveyInDb);
}
foreach (var survey in entity.SurveyPoints)
{
// Is the child NOT in DB?
if (surveyPointsInDb.All(i => i.Id != survey.Id))
// Yes: Add it as a new child
currentWell.SurveyPoints.Add(survey);
}
// Update Temperatures - THIS IS A HUGE PAIN = HOPE EF is updated to handle updating disconnected graphs.
var temperaturesInDb = currentWell.Temperatures.ToList();
foreach (var tempInDb in temperaturesInDb)
{
// Is the geometry item still there?
var temperature = entity.Temperatures.SingleOrDefault(i => i.Id == tempInDb.Id);
if (temperature != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(tempInDb).CurrentValues.SetValues(temperature);
else
// No: Delete it
TheContext.WellTemperaturePoints.Remove(tempInDb);
}
foreach (var temps in entity.Temperatures)
{
// Is the child NOT in DB?
if (surveyPointsInDb.All(i => i.Id != temps.Id))
// Yes: Add it as a new child
currentWell.Temperatures.Add(temps);
}
await TheContext.SaveChangesAsync();
return Ok(model);
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
return InternalServerError();
}
This is a huge pain to me too. I extracted the answer from #GetFuzzy to a more reusable method:
public void UpdateCollection<TCollection, TKey>(
DbContext context, IList<TCollection> databaseCollection,
IList<TCollection> detachedCollection,
Func<TCollection, TKey> keySelector) where TCollection: class where TKey: IEquatable<TKey>
{
var databaseCollectionClone = databaseCollection.ToArray();
foreach (var databaseItem in databaseCollectionClone)
{
var detachedItem = detachedCollection.SingleOrDefault(item => keySelector(item).Equals(keySelector(databaseItem)));
if (detachedItem != null)
{
context.Entry(databaseItem).CurrentValues.SetValues(detachedItem);
}
else
{
context.Set<TCollection>().Remove(databaseItem);
}
}
foreach (var detachedItem in detachedCollection)
{
if (databaseCollectionClone.All(item => keySelector(item).Equals(keySelector(detachedItem)) == false))
{
databaseCollection.Add(detachedItem);
}
}
}
With this method in place I can use it like this:
public void UpdateProduct(Product product)
{
...
var databaseProduct = productRepository.GetById(product.Id);
UpdateCollection(context, databaseProduct.Accessories, product.Accessories, productAccessory => productAcccessory.ProductAccessoryId);
UpdateCollection(context, databaseProduct.Categories, product.Categories, productCategory => productCategory.ProductCategoryId);
...
context.SubmitChanges();
}
However when the graph gets deeper, I have a feeling this will not be sufficient.
What your looking for is the Unit of Work pattern:
http://msdn.microsoft.com/en-us/magazine/dd882510.aspx
You can either track UoW on the client and pass it in with the DTO or have the server figure it out. Both the veritable DataSet and EF Entities have their own internal implementation of UoW. For something stand alone there is this framework, but I have never used it so have no feedback:
http://genericunitofworkandrepositories.codeplex.com/
Alternatively another option is to do real time updates with undo functionality, kind of like when you go into Gmail contacts and it saves the changes as you make them with the option to undo.
It depends HOW you are accomplishing adding/changing the entities.
I think you may be trying to do too much with an entity at any given time. Allowing editing and adding at the same time can get you into a situation where your not sure what is being done with the entity, especially in a disconnected scenario. You should only perform a single action on a single entity at a time, unless you are deleting entities. Does this seem monotonous, sure, but 99% of your users want a clean and easily understandable interface. Many time we end up making screens of our applications "god" screens where everything and anything can be done. Which 9/10 times isn't needed (YAGNI).
This way, when you edit a user, you know you are doing an update operation. If you are adding a new maintenance record, you know you are creating a new record that is attached to an automobile.
To summarize, you should limit how many operations you are making available for a single screen and make sure you provide some type of unique information for the entity so you can try to look up the entity to see if it exists.
I had the similar problem, and couldnt find my own solution. I think that problem is complex. Complete solution for updating graphs in disconected scenario with EF6 I find in extension method RefactoringThis.GraphDiff produced by Brent McKendric.
Exemple brings by author is:
using (var context = new TestDbContext())
{
// Update the company and state that the company 'owns' the collection Contacts.
context.UpdateGraph(company, map => map
.OwnedCollection(p => p.Contacts, with => with
.AssociatedCollection(p => p.AdvertisementOptions))
.OwnedCollection(p => p.Addresses)
);
context.SaveChanges();
}
See more at:
http://blog.brentmckendrick.com/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/

Categories