EFCore Update isn't updating rows in the database - c#

I've got a payload coming up from my client that is an object whose properties are primitives and other objects whose properties are also primitives.
For example:
public class MainObj
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id {get;set;}
public string foo {get;set;}
public OtherObj bar {get;set;}
}
public class OtherObj
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id {get;set;}
public int test {get;set;}
}
Once I validate the request, I get the object the user is trying to update:
var obj = _context.MainObjs.Select(x => new MainObj
{
id = x.id,
foo = x.foo,
bar = x.bar
}).SingleOrDefaultAsync(x => x.id == request.id);
I'm doing the .Select because if I don't, then bar never gets populated.
Then I update the properties with what comes up from the client:
obj.foo = request.foo;
obj.bar = request.bar;
Then I try to save changes:
_context.SaveChangesAsync();
However, nothing is persisting in the database when I do this. What am I doing wrong? I've only worked with EF6 in the past, so I dunno if EFCore has something weird about updating objects with foreign key relationships. And to be clear, my actual objects have many more properties than this, but I don't believe that's the problem.
EDIT: I tried using .Include syntax instead of .Select , which looks like this:
var obj = _context.MainObjs.Include(x =>x.bar).SingleOrDefaultAsync(x => x.id == request.id);
but then I get an error that The instance of entity type cannot be tracked because another instance of this type with the same key is already being tracked.
EDIT2: Replacing all of that other code with simply _context.Update(request) is working. I'm curious why the other way isn't though.

The statement ...
_context.MainObjs.Select(x => new MainObj { ... })
... doesn't attach anything to the context. It just creates a new MainObj object, but EF doesn't know about it. In EF6 it wasn't allowed to create entity types in EF LINQ queries, exactly to prevent this confusion.
So by using _context.MainObjs.Include(x =>x.bar), you do attach a MainObj and its bar to the context and its changes will be tracked.
Finally, the statement _context.Update(request) attaches request to the context and marks it as Modified, including its foreign key to its bar property.

Related

Foreign key not updated in Entity Framework

I'm running into a strange situation:
public Class A
{
public int Id { get; set; }
}
public class B
{
public int Id { get; set; }
[ForeignKey("A1")]
public virtual int A_Id { get; set; }
public virtual A A1 { get; set; }
}
When I update an entity of type B, by modifying A1, A1.Id is updated to the new entity Id, but B.A_Id still remains assigned to the old Id. This causes Entity Framework to throw an error.
I had read that by marking both properties as virtual, EF change tracker would automatically detect the change and update the related foreign key, but this doesn't happen for me. What else can I check?
How you map the relationship between A & B will determine the behavior. You do not need to mark the A_Id as Virtual.
Provided your real schema is mapped out like above, it should just work as a typical many-to-one mapping. (Many B's can reference an A) The Key point is that the A_Id on B will not be updated until you call SaveChanges on the DbContext. Once SaveChanges is called, the FK will be updated to reflect the different A.
For instance:
using (var context = new TestDbContext())
{
var a = context.As.Single(x => x.Id == 1);
var b = context.Bs.Single(x => x.Id == 1);
Assert.AreEqual(2, b.A_id);
b.A = a;
context.SaveChanges();
Assert.AreEqual(1, b.A_Id);
}
When B was loaded it was referencing an A with ID = 2. We load A ID #1 and associate it to B using the reference. Once SaveChanges is called we can assert that B's A_Id now reflects the link to A ID 1.
Beyond that you may be encountering issues depending on how/where your A and B references are loaded. Ensure that they are coming from the same DbContext instance. A big problem I see people having is by passing references to entities around. This often leads to exceptions when trying to update references within the scope of a DbContext using entities that were loaded elsewhere.
If you are still running into issues or suspect something like above, include a copy of your exception message and actual code and we can take it from there.

Insert entity with one related entity which existed in the database

I am stuck at the operation when using Entity Framework Core 2 to perform an insert of a new entity with one related entity which already existed in the database. For example, I have two objects with one to many relationship:
public Class OrderDetail
{
public int ParentID {get;set;}
public string OrderDetailName {get;set;}
public int ProductID {get;set;}
public virtual Product ProductFK {get;set;}
}
public Class Product
{
public int ProductID {get;set;}
public string ProductName {get;set;}
public virtual Collection<OrderDetail> OrderDetails {get;set;} = new Collection<OrderDetail>();
}
I would like to add a new OrderDetail with an existing Product (with productID = 3) into the database, so I perform like:
private void AddOrderDetail(int productID)
{
OrderDetail newOrderDetail = new OrderDetail();
newOrderDetail.Name = "New OrderDetail";
// Here I have no clue what would be the correct way...Should I do
// Approach A(pick related entity from database then assign as related entity to base entity):
var ProductFromDB = DbContext.Products.SingleOrDefault(p => p.ProductID == productID);
newOrderDetail.ProductFK = ProductFromDB;
// Approach B(directly assign the foreign key value to base entity)
newOrderDetail.ProductID = productID
DbContext.SaveChange();
}
By using approach (A), my newOrderDetail failed to save and I looked into SQL resource, looks like it considered the one that I retrieved from the database (ProductFromDB) as a new object and was trying to insert it again. I feel it's redundant job for picking ProductFromDB first then assign to the inserted entity...
By using approach (B), my newOrderDetail still failed to save, and I am getting an exception like "insert A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.", however, this exception does not happen constantly. I looked into the SQL and found the SQL Script, by running it individually in SQL Server it worked, however when running an application side, it's not working...
So what would be the correct way to deal with above scenario?
If you don't need access to the complete Product object right away, you could try to set just the foreign key column value of your newOrderDetail. Also, you need to ADD your newly created OrderDetail object to the DbContext before saving - something like this:
private void AddOrderDetail(int productID)
{
OrderDetail newOrderDetail = new OrderDetail();
newOrderDetail.Name = "New OrderDetail";
newOrderDetail.ProductID = productID
// OF COURSE, you need to ADD the newly created object to the context!
DbContext.OrderDetails.Add(newOrderDetail);
DbContext.SaveChange();
}
Next time around, when you actually fetch the OrderDetail, it will resolve the linked product and you should be fine.:
using (var DbContext = new YourDbContext())
{
OrderDetail od = DbContext.OrderDetails.FirstOrDefault(x => x.Name = "New OrderDetail");
....
}

EF 5 Code First - Why is explicitly loaded data not accessible from my model?

I have the following two model objects which have a many-to-many relationship:
public class StaffMember
{
public Guid StaffMemberKey {get; set;}
// lots of other properties that aren't relevant
public ICollection<Case> Cases {get; set;}
}
public class Case
{
public int CaseKey {get; set;}
// lots of other properties that aren't relevant
public ICollection<StaffMember> Staff {get; set;}
}
The mapping for the many-to-many relationship is handled in the configuration for the Case entity:
public class CaseMapping : EntityTypeConfiguration<Case>
{
public CaseMapping()
{
// other property and relationship mappings
// Many-to-Many mapping with Staff Members
HasMany(c => c.Staff)
.WithMany(staffMember => staffMember.Cases)
.Map(m =>
{
m.ToTable("Cases_StaffMembers", "dbo");
m.MapLeftKey("CaseKey");
m.MapRightKey("StaffMemberKey");
});
}
}
Everything is working great in terms of being able to query against this relationship, add, delete, etc. However, when trying to explicitly load and filter staff members for a case, as described here, no data is being loaded in to the appropriate collection of related entities.
Here is an example of what I'm attempting do:
var staffMemberKey = Guid.Parse("...");
var caseKey = 5;
using (var context = new CodeFirstContext())
{
var selectedCase = context.Cases.Find(caseKey);
context.Entry(selectedCase).Collection(c => c.Staff).Query().Where(sm => sm.StaffMemberKey == staffMemberKey).Load();
}
I would expect that selectedCase.Staff would contain the staff member that was loaded, but it remains null. If I call ToList() instead of Load when querying for the related data, the resulting list does contain the correct staff member entity. If I simply call context.Entry(selectedCase).Collection(c => c.Staff).Load();, then the data is loaded as expected. Is there something I'm missing? What gives?
As a final note, I have lazy loading and proxy creation disabled for my context, in case that makes any difference in this scenario.
When you call Query(), it returns an IQueryable that gives the entities that would be in that property -- it is not designed to be used to update the property. It is basically a "shortcut" for:
ctx.Staff.Where(staff => staff.Case.Id == caseKey);
Load() will load entities into your context, as if you had called ToList() but without returning anything. It works on any IQueryable, and does not capture anything related to the Entry().

Updating many-to-many relationships with a generic repository

I have a database context with lazy loading disabled. I am using eager loading to load all of my entities. I cannot update many to many relationships.
Here's the repository.
public class GenericRepository<TEntity> : IGenericRepository<TEntity>
where TEntity : class
{
... other code here...
public virtual void Update(TEntity t)
{
Set.Attach(t);
Context.Entry(t).State = EntityState.Modified;
}
...other code here...
}
Here's the User model.
public partial class User
{
public User()
{
this.Locks = new HashSet<Lock>();
this.BusinessModels = new HashSet<BusinessModel>();
}
public int UserId { get; set; }
public string Username { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string JobTitle { get; set; }
public string RecoveryEmail { get; set; }
public Nullable<double> Zoom { get; set; }
public virtual ICollection<Lock> Locks { get; set; }
public virtual ICollection<BusinessModel> BusinessModels { get; set; }
}
If I modify the business models collection, it does not save the business models collection although I have attached the entire entity.
Worker.UserRepository.Update(user);
I'm not sure what is going on. I don't want to break my generic repository/unit of work pattern just to update many-to-many relationships.
Edit 2: I've got this working...but it is extremely different from the pattern that I'm going for. Having hard implementations means I will need to create a method for each type that has a many to many relationship. I am investigating now to see if I can make this a generic method.
Edit 3: So the previous implementation I had did not work like I thought it would. But now, I have a slightly working implementation. If someone would please help me so I can move on from this, I will love you forever.
public virtual void Update(TEntity updated,
IEnumerable<object> set,
string navigationProperty,
Expression<Func<TEntity, bool>> filter,
Type propertyType)
{
// Find the existing item
var existing = Context.Set<TEntity>().Include(navigationProperty).FirstOrDefault(filter);
// Iterate through every item in the many-to-many relationship
foreach (var o in set)
{
// Attach it if its unattached
if (Context.Entry(o).State == EntityState.Detached)
// Exception "an object with the same key already exists"
// This is due to the include statement up above. That statement
// is necessary in order to edit the entity's navigation
// property.
Context.Set(propertyType).Attach(o);
}
// Set the new value on the navigation property.
Context.Entry(existing).Collection(navigationProperty).CurrentValue = set;
// Set new primitive property values.
Context.Entry(existing).CurrentValues.SetValues(updated);
Context.Entry(existing).State = EntityState.Modified;
}
I then call it like this:
Worker.UserRepository.Update(user, user.BusinessModels, "BusinessModels", i => i.UserId == user.UserId, typeof (BusinessModel));
Extremely messy, but it lets me update many-to-many relationships with generics. My big problem is the exception when I go to attach new values that already exist. They're already loaded because of the include statement.
This works:
This doesn't:
After many painful hours, I have finally found a way to update many-to-many relationships with a completely generic repository. This will allow me to create (and save) many different types of entities without creating boilerplate code for each one.
This method assumes that:
Your entity already exists
Your many to many relationship is stored in a table with a composite key
You are using eager loading to load your relationships into context
You are using a unit-of-work/generic repository pattern to save your entities.
Here's the Update generic method.
public virtual void Update(Expression<Func<TEntity, bool>> filter,
IEnumerable<object> updatedSet, // Updated many-to-many relationships
IEnumerable<object> availableSet, // Lookup collection
string propertyName) // The name of the navigation property
{
// Get the generic type of the set
var type = updatedSet.GetType().GetGenericArguments()[0];
// Get the previous entity from the database based on repository type
var previous = Context
.Set<TEntity>()
.Include(propertyName)
.FirstOrDefault(filter);
/* Create a container that will hold the values of
* the generic many-to-many relationships we are updating.
*/
var values = CreateList(type);
/* For each object in the updated set find the existing
* entity in the database. This is to avoid Entity Framework
* from creating new objects or throwing an
* error because the object is already attached.
*/
foreach (var entry in updatedSet
.Select(obj => (int)obj
.GetType()
.GetProperty("Id")
.GetValue(obj, null))
.Select(value => Context.Set(type).Find(value)))
{
values.Add(entry);
}
/* Get the collection where the previous many to many relationships
* are stored and assign the new ones.
*/
Context.Entry(previous).Collection(propertyName).CurrentValue = values;
}
Here's a helper method I found online which allows me to create generic lists based on whatever type I give it.
public IList CreateList(Type type)
{
var genericList = typeof(List<>).MakeGenericType(type);
return (IList)Activator.CreateInstance(genericList);
}
And from now on, this is what calls to update many-to-many relationships look like:
Worker.UserRepository.Update(u => u.UserId == user.UserId,
user.BusinessModels, // Many-to-many relationship to update
Worker.BusinessModelRepository.Get(), // Full set
"BusinessModels"); // Property name
Of course, in the end you will need to somewhere call:
Context.SaveChanges();
I hope this helps anyone who never truly found how to use many-to-many relationships with generic repositories and unit-of-work classes in Entity Framework.
#dimgl Your solution worked for me. What I've done in addition was to replace the hard-coded type and name of the primaryKey with dynamically retrieved ones:
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
ObjectSet<TEntity> set = objectContext.CreateObjectSet<TEntity>();
IEnumerable<string> keyNames = set.EntitySet.ElementType.KeyMembers.Select(k => k.Name);
var keyName = keyNames.FirstOrDefault();
var keyType = typeof(TEntity).GetProperty(keyName).PropertyType
foreach (var entry in updatedSet
.Select(obj =>
Convert.ChangeType(obj.GetType()
.GetProperty(keyName)
.GetValue(obj, null), keyType))
.Select(value => context.Set<TEntity>().Find(value)))
{
values.Add(entry);
}
Like this your code won't depend on the Entity key's name and type.

Mapping a DTO to an Entity with Automapper

I have an Entity Framework POCO with the following structure.
public class Entity
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
I've created a Data Transfer Object for this entity to be used by my views.
public class EntityDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Now, I have the following mapping code in my Global.asax file.
Mapper.CreateMap<Entity, EntityDto>();
Mapper.CreateMap<EntityDto, Entity>(); // not sure whether I need this as well?
Everything is working fine, I pass the DTO to my views OK and I can create a new instance of Entity from my EntityDto model. The problem arises when I try to edit my Entity; I'm aware this is down to AutoMapper losing the Entity Key that EF creates to track changes to the object, but having read through a few sources there doesn't seem to be a definitive solution. Here is the action I'm using to edit my entity.
public ActionResult EditEntity(EntityDto model)
{
var entity = context.Entities.Single(e => e.Id == model.Id);
entity = Mapper.Map<EntityDto, Entity>(model); // this loses the Entity Key stuff
context.SaveChanges();
return View(model);
}
Now, what do I do to solve this? Can I:
Somehow tell AutoMapper to .Ignore() the Entity Key properties?
Get AutoMapper to copy out the Entity Key properties?
.Attach() my mapped Entity and set the state to modified?
Any help always appreciated.
Try passing entity as a second parameter to your mapping.
entity = Mapper.Map<EntityDto, Entity>(model, entity);
Otherwise, your entity instance is overwritten with a new instance, and you lose the entity created in the first line.
.Attach() my mapped Entity and set the state to modified?
public ActionResult EditEntity(EntityDto model)
{
var entity = Mapper.Map<Entity>(model);
context.Set<Entity>().Attach(entity); // (or context.Entity.Attach(entity);)
context.Entry<Entity>(entity).State = System.Data.EntityState.Modified;
context.SaveChanges();
return View(model);
}
Where is your context instantiated? You should do that in your EditEntity action imo.
public ActionResult EditEntity(EntityDto model)
{
using(var context = new MyContext())
{
var entity = Mapper.Map<Entity>(model);
context.Set<Entity>().Attach(entity); // (or context.Entity.Attach(entity);)
context.Entry<Entity>(entity).State = System.Data.EntityState.Modified;
context.SaveChanges();
return View(model);
}
}
An alternative answer that doesn't require Automapper for the DTO to Entity conversion is using a DbEntry:
var oldEntity = DbSet.FirstOrDefault(x => x.Id == updatedEntity.Id);
var oldEntry = Context.Entry(oldEntity);
oldEntry.CurrentValues.SetValues(updatedEntity);
You don't need any attach/state checking because you are getting the old entity first so it has change tracking attached to it. Also, the CurrentValues.SetValues can accept a different type, in this example updatedEntity is the DTO. Set Values documentation is explained as such:
Sets the values of this dictionary by reading values out of the given object. The given object can be of any type. Any property on the object with a name that matches a property name in the dictionary and can be read will be read. Other properties will be ignored. This allows, for example, copying of properties from simple Data Transfer Objects (DTOs).
So seems like it already can perform in an automapper-esque way.

Categories

Resources