Adding and deleting many-to-many using DbContext API - c#

I am using Entity Framework and DbContext API do build my application but I am having trouble working with objects with many-to-many relations. A simplified save-method could look like this
public void MyObj_Save(MyObj myobj)
{
DbContext.Entry(myobj).State = EntityState.Added;
DbContext.SaveChanges();
}
This code works fine, but if MyObj contains a many-to-many relation this is not saved. I know from using the old POCO API, that I needed to attach the related objects to the context but I cannot find a way to do this correctly with the DbContext API - a simplified example below
public void MyObj_Save(MyObj myobj, List<OtherObj> otherObjList)
{
foreach (OtherObj otherObj in otherObjList)
{
DbContext.OtherObj.Attach(otherObj);
myobj.OtherObj.Add(otherObj);
}
DbContext.Entry(myobj).State = EntityState.Added;
DbContext.SaveChanges();
}
I get no error, but the relations are not saved. What to do?

I quote your (important!) comment:
The objects I send to the method are attached and EntityState is
Unchanged. The configuration of my DbContext is, that I have disabled
AutoDetectChangesEnabled...
So, your code would look like this:
DbContext.Configuration.AutoDetectChangesEnabled = false;
DbContext.Entry(myobj).State = EntityState.Unchanged;
foreach (OtherObj otherObj in otherObjList)
DbContext.Entry(otherObj).State = EntityState.Unchanged;
// entering MyObj_Save method here
foreach (OtherObj otherObj in otherObjList)
{
//DbContext.OtherObj.Attach(otherObj); // does not have an effect
myobj.OtherObj.Add(otherObj);
}
DbContext.Entry(myobj).State = EntityState.Added;
DbContext.SaveChanges();
And this indeed doesn't work because EF doesn't notice that you have changed the relationship between myobj and the list of OtherObj in the line myobj.OtherObj.Add(otherObj); because you have disabled automatic change detection. So, no entries will be written into the join table. Only myobj itself will be saved.
You cannot set any state on an entity to put the state manager into a state that the relationship is saved because it is not an entity state which is important here but a relationship state. These are separate entries in the object state manager which are created and maintained by change detection.
I see three solution:
Set DbContext.Configuration.AutoDetectChangesEnabled = true;
Call DetectChanges manually:
//...
DbContext.Entry(myobj).State = EntityState.Added;
DbContext.ChangeTracker.DetectChanges();
DbContext.SaveChanges();
Detach the new myobj from the context before you set it into Added state (this feels very hacky to me):
// entering MyObj_Save method here
DbContext.Entry(myobj).State = EntityState.Detached;
foreach (OtherObj otherObj in otherObjList)
//...
Maybe it is possible - by getting to the ObjectContext through the IObjectContextAdapter - to modify the relationship entries in the object state manager manually but I don't know how.
In my opinion, this procedure to manipulate entity (and relationship) states manually is not the way you are supposed to work with EF. AutoDetectChangesEnabled has been introduced to make working with EF easier and safer and the only recommended situation to disable it is a high performance requirement (for example for bulk inserts). If you disable automatic change detection without need you are running into problems like this which are difficult to detect and it requires advanced knowledge of EF's inner workings to fix those bugs.

public void MyObj_Save(MyObj myobj, List<OtherObj> otherObjList)
{
DbContext.Entry(myobj).State = EntityState.Added;
foreach (OtherObj otherObj in otherObjList)
{
(((System.Data.Entity.Infrastructure.IObjectContextAdapter)DbContext)
.ObjectContext)
.ObjectStateManager
.ChangeRelationshipState(myobj, otherObj,
q => q.OtherObjs, EntityState.Added);
}
DbContext.SaveChanges();
}
Again, it is a simplified and not a real life example!

Related

The instance of the entity type cannot be tracked because another instance with the keyvalue is being tracked

I am basically trying to implement CRUD using EntityFrameWork core and .Net core 3.1. I have an issue with my update operation where I am not able update the context with the modified value.
I am using postman to initiate the request.
As you can see in the code below, I am trying to check if that customer exist and if it does pass the modified object to the context.
Function code
[FunctionName("EditCustomer")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous,"post", Route = "update-customer")] HttpRequest req)
{
var customer = JsonConvert.DeserializeObject<CustomerViewModel>(new StreamReader(req.Body).ReadToEnd());
await _repo.UpdateCustomer(customer);
return new OkResult();
}
Repository method
public async Task UpdateCustomer(CustomerViewModel customerViewModel)
{
if (customerViewModel.CustomerId != null)
{
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
if (customer == null)
{
throw new Exception("customer not found");
}
else
{
_context.Customers.Update(_mapper.Map<Customers>(customerViewModel));
await _context.SaveChangesAsync();
}
}
}
Mapping
public class CustomerManagerProfile : Profile
{
public CustomerManagerProfile()
{
CreateMap<CustomerDetails, CustomerDetailsViewModel>().ReverseMap();
CreateMap<CustomerOrders, CustomerOrdersViewModel>().ReverseMap();
CreateMap<CustomerOrderDetails, OrderDetailsViewModel>().ReverseMap();
CreateMap<Customers, CustomerViewModel>().ReverseMap();
}
}
Solution
public async Task UpdateCustomer(CustomerViewModel customerViewModel)
{
if (customerViewModel.CustomerId != null)
{
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
if (customer == null)
{
throw new Exception("customer not found");
}
else
{
var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Entry<Customers>(customer).State = EntityState.Detached;
_context.Entry<Customers>(customerModel).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
}
}
Entity Framework tracks your entities for you. For simplicity's sake, think of it like keeping a dictionary (for every table) where the dictionary key is equal to your entity's PK.
The issue is that you can't add two items of the same key in a dictionary, and the same logic applies to EF's change tracker.
Let's look at your repository:
var customer = _context
.Customers
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
The fetched customer is retrieved from the database and the change tracker puts it in his dictionary.
var mappedCustomer = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update();
I split your code in two steps for the sake of my explanation.
It's important to realize that EF can only save changes to tracked objects. So when you call Update, EF executes the following check:
Is this the same (reference-equal) object as one I have I my change tracker?
If yes, then it's already in my change tracker.
If not, then add this object to my change tracker.
In your case, the mappedCustomer is a different object than customer, and therefore EF tries to add mappedCustomer to the change tracker. Since customer is already in there, and customer and mappedCustomer have the same PK value, this creates a conflict.
The exception you see is the outcome of that conflict.
Since you don't need to actually track your original customer object (since EF doesn't do anything with it after fetching it), the shortest solution is to tell EF to not track customer:
var customer = _context
.Customers
.AsNoTracking()
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
Since customer is now not put into the change tracker, mappedCustomer won't cause a conflict anymore.
However, you don't actually need to fetch this customer at all. You're only interested in knowing whether it exists. So instead of letting EF fetch the entire customer object, we can do this:
bool customerExists = _context
.Customers
.Any(c => c.CustomerId.Equals(customerViewModel.CustomerId));
This also solves the issue since you never fetch the original customer, so it never gets tracked. It also saves you a bit of bandwidth in the process. It's admittedly negligible by itself, but if you repeat this improvement across your codebase, it may become more significent.
The most simple adjustment that you could make would be to avoid tracking your Customers on retrieval like this:
var customer = _context
.Customers
.AsNoTracking() // This method tells EF not to track results of the query.
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
It's not entirely clear from the code, but my guess is your mapper returns a new instance of Customer with the same ID, which confuses EF. If you would instead modify that same instance, your call to .Update() should work as well:
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
_context.Customers.Update(customer);
await _context.SaveChangesAsync();
As a matter of fact, if you track your Customer you don't even need to explicitly call .Update() method, the purpose of tracking is to be aware of what changes were made to the entities and should be saved to the database. Therefore this will also work:
// Customer is being tracked by default.
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
await _context.SaveChangesAsync();
EDIT:
The solution you yourself provide begins by tracking the results of your query (the Customer) instance, then stops tracking it (a.k.a. gets detached) before writing to database and instead starts tracking the instance that represents the updated Customer and also marks it as modified. Obviously that works as well, but is just a less efficient and elegant way of doing so.
As a matter of fact if you use this bizarre approach, I don't see the reason for fetching your Customer at all. Surely you could just:
if (!(await _context.Customers.AnyAsync(c => c.CustomerId == customerViewModel.CustomerId)))
{
throw new Exception("customer not found");
}
var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update(customerModel);
await _context.SaveChangesAsync();
You use AutoMapper wrong way. It is not created to map from View model or DTO to Entity classes. It makes many problems and you are facing with only one of them now.
If you have more complex bussiness logic in you app (not just udpate all fields), it will be horrible to manage, test and debug what actually is happening in your code. You should write you own logic with some bussiness validation in case when you want to make some other update than CRUD.
If I were you I would create UpdateFields method in Customer class which would update them and finally call SaveChanges. It depends on whether you use anemic entity (anti)pattern or not. If you do not want your entity class to have any method you can create just method which manually map you VM do entity with some domain validation

Difference between context.entity.Attach(entity) and context.entity.Add(entity)

I'm new in Entity Framework and when i working with disconnected context , i faced with a question.
What is difference between Context.entity.Attach() And Context.entity.Add()
In updating a data?
I khow about Disconnected and
And i khow i can update a data in Entity only with getting object from database and change property with Setters and saving changes and Connected scenario in Entity
In addition i make sure that i search in all of Stack Overflow but i didn't find simple definition about this.
Edited :
My code without attach and add :
static void update(Employee emp)
{
using(var context=new EmployeeCtx()))
{
var find=context.find(emp.id);
find.name="new name";
context.saveChanges();
}
}
EDIT 2:
as i realized from users that comment to my post and Entity Framework Disconnected Scenario
context.entity.Attach(obj)will add entity to context with Unchanged state and you should add state with context.Entry(Entity).State=EntityState.Added but context.entity.Add(obj) will do this as pretty as possible
(easier to use) Thanks from who answered me and Entity Framework Disconnected Scenario
Notice : this edit is for Disconnected Entity From Context
EDIT 3: please read all of the comments , that is so helpful.
I think you mean Code-first not code-based :) Here's a good explanation
Entity Framework Add and Attach and Entity States
Adding rewrite (plug in your dbset name and handle not found):
static void update(Employee emp)
{
using(var context = new EmployeeCtx())
{
var currentRecord = context.<dbsetname>.First(p => p.id == emp.id);
if (currentRecord == null)
// Handle not found condition
else
{
currentRecord.name = emp.name;
context.SaveChanges();
}
}
}

EF, Update doesnt work, it says Entities may have been modified or deleted since entities were loaded.

I am trying to create a layered MVC project but I am having an UPDATE problem in EF. I am getting the following error.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.
I have DAL and BusinessLayer. In DAL, I have the following code for UPDATE
public void Update(params T[] entities)
{
using (var context = new BorselDBEntities())
{
foreach (T entity in entities)
{
context.Entry(entity).State = EntityState.Modified;
}
context.SaveChanges();
}
}
and this is how I call the DAL from BusinessLayer
public void UpdateProduct(params Product[] products)
{
_productRepository.Update(products);
}
Why am I getting the error above and what could I do to fix it?
One common reason is that context.Entry(entity) fails to get the entity which you want to update.
When you're debugging, see if context.Entry(entity) returns the entity; easily done by putting it on a separate line and setting a breakpoint afer:
public void Update(params T[] entities)
{
using (var context = new BorselDBEntities())
{
foreach (T entity in entities)
{
var myEntity = context.Entry(entity);
myEntity.State = EntityState.Modified;
}
context.SaveChanges();
}
}
If it's not, you'll need to work back through your code and work out why it's not able to pick it up. Often this will be because the identity/primary key column is not set on 'entity'.
E.g. in an MVC application, if you have an Edit/update form, remember to have a
#Html.HiddenFor(model => model.Id)
It is necessary to call the Attach method on the DbSet for the entity you are updating before you can change it's State. The local DbContext needs to contain the Entity or it will not know what changes to track.
https://msdn.microsoft.com/en-us/library/gg696261(v=vs.103).aspx

entity records not updating

I have the following method:
public static void UpdatePpsTransaction(IEnumerable<PpsTransaction> ppsTransaction)
{
using (var context = PpsEntities.DefaultConnection())
{
foreach (var trans in ppsTransaction)
{
context.PpsTransactions.Attach(trans);
}
context.SaveChanges();
}
}
}
I was removing these records but I ended up creating a field IsProcessed which I am now setting to true. Now I am updating the records instead of deleting them, keeping them for record keeping.
Anyhow, I am not getting any errors but it is not updating the record.
Any suggestions?
You're not telling EF that you have made any changes, try to use the Entry method, on your Context:
public static void UpdatePpsTransaction(IEnumerable<PpsTransaction> ppsTransaction)
{
using (var context = PpsEntities.DefaultConnection())
{
foreach (var trans in ppsTransaction)
{
context.Entry(trans).State = EntityState.Modified;
}
context.SaveChanges();
}
}
This way, the entities will be attached to the context in a modified stated, so when you call SaveChanges(), these will be saved.
This will only work if the entities already exists in the database, which they should.
From msdn:
If you have an entity that you know already exists in the database but which is not currently
being tracked by the context then you can tell the context to track
the entity using the Attach method on DbSet. The entity will be in the
Unchanged state in the context. Note that no changes will be made to the
database if SaveChanges is
called without doing any other manipulation of the attached entity.
This is because the entity is in the Unchanged state.
Here is the link

How to save combined (new+modified) detached entities in Entity Framework?

What is the proper and fast way to save combined new and modified detached POCO entities?
I was thinking about these methods:
private void Method_2(IList<Entity> entities) //detached entities
{
//This method is using SELECT to check if entity exist
using (var context = new ModelContainer())
{
foreach (Entity entity in entities)
{
var foundEntity = context.CreateObjectSet<Entity>().SingleOrDefault(t => t.Id == entity.Id);
context.Detach(foundEntity); //Remove it from ObjectStateManager
if (foundEntity != null)//It is modified entity
{
context.AttachTo("EntitySet", entity); //Attach our entity
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified); //We know it exists
}
else//It is new entity
{
context.CreateObjectSet<Entity>().AddObject(entity);
}
}
context.SaveChanges();
}
}
private void Method_1(IList<Entity> entities) //detached entities
{
//This method doesn't select anything from DB, but i have ta call Savechanges after each object
using (var context = new ModelContainer())
{
foreach (Entity entity in entities)
{
try
{
context.AttachTo("EntitySet", entity);
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
context.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Added);
context.SaveChanges();
}
}
}
}
When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.
Well i agree with this statement if you found yourself in situation when you need to use EF code like this in EF definitely something is wrong with you decision. I have chosen wrong tool for this job.
When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.
The very easy way is:
foreach (var entity in entities)
{
if (entity.Id == 0) // 0 = default value: means new entity
{
// Add object
}
else
{
// Attach object and set state to modified
}
}
The example requires that you have some db auto-generated primary key (Id).
Your Method 2 is possible with some modifications. It is not needed to detach entity when you load it. Instead use ApplyCurrentValues. The approach with loading entity first is very usefull when you decide to work with object graphs instead of single entity. But in the case of object graph you have to do synchronization manually. ApplyCurrentValues works only for scalar (non navigation) properties. You can try to futher optimize your method to load needed enitites in single roundtrip to database instead of loading entities one by one.
Your Method 1 is terrible solution. Using exceptions raised on database server to control program flow is bad approach.
I agree with #Ladislav - Method_1 is a bad approach. Let the database raise exceptions which are caught by EF - don't try and swallow these exceptions yourself.
Your on the right track with Method 1.
Here is how i do it - as i also have a detached context (POCO's, no change tracking, ASP.NET MVC).
BLL Interface: (note i have TPT in my model, hence generics. "Post" is abstract)
void Add(Post post);
void Update<TPost>(TPost post) where TPost : Post, new();
The new() constraint is crucial - you'll see why shortly.
I won't show how i do "Add", because it's simple as you think - AddObject(entity);
The "Update" is the tricky part:
public class GenericRepository<T> : IRepository<T> where T : class
{
public void Update<T2>(T2 entity) where T2: class, new()
{
var stub = new T2(); // create stub, now you see why we need new() constraint
object entityKey = null;
// ..snip code to get entity key via attribute on all domain entities
// once we have key, set on stub.
// check if entity is already attached..
ObjectStateEntry entry;
bool attach;
if (CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), stub), out entry))
{
// Re-attach if necessary.
attach = entry.State == EntityState.Detached;
}
else
{
// Attach for first time.
attach = true;
}
if (attach)
CurrentEntitySet.Attach(stub as T);
// Update Model. (override stub values attached to graph)
CurrentContext.ApplyCurrentValues(CurrentContext.GetEntityName<T>(), entity);
}
}
And that works for me.
As for the entity key, i have used attributes on my domain classes. An alternative (which i'm about to move to), is have all my domain entities implement an interface, which specifies that all domain entities must have a property called "EntityKey". Then i'll use that interface on my constraints. Basically, i needed a dynamic way to create stub entities in a generic repository.
I don't personally like the idea of "checking the ID, if its > 0 then it's an update". Because i'm working with ASP.NET MVC, if i (or another developer) forgets to bind the ID to the View, it won't be passed through, so even though it may be an update, because the ID == 0 it will be added.
I like to be explicit about the operations. This way, i can perform Add/Update seperate validation logic.
Perhaps take a look at Self Tracking POCO entities. IMHO they are perfect for any scenario that requires the entity to be separated from the context. It takes care of all the plumbing code for you.

Categories