EF Core 2.0 update - tracking issue - postgresql

I am getting changed entity from fronted, mapping it on backend side and simply want to update it in database.
Update is performing like this:
[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody]Worker worker)
{
using (var dbContext= new MyDbContext())
{
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var entity = dbContext.Workers.FirstOrDefault(r => r.Id == worker.Id);
if (entity == null) return BadRequest();
dbContext.Workers.Update(worker);
dbContext.SaveChanges();
return Ok();
}}
Before this action, i am getting the list of users and sending it to frontend.
Although I set QueryTrackingBehavior to NoTracking, i am getting exception:
System.InvalidOperationException: 'The instance of entity type 'Contract' cannot be tracked because another instance with the key value 'Id:4' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'
Where Contract is related entity for Worker which is updated...
Any idea what i am doing wrong here?
UPDATE:
Worker - Contract relation:
public class Worker: IId
{
public int Id { get; set; }
public ICollection<Contract> Contracts{ get; set; }
}
public class Contract: IId
{
public int Id { get; set; }
public int WorkerId { get; set; }
public Worker Worker { get; set; }
}

Okay! got the problem in your code. You didn't map the updated entity to the existing entity that you pulled from the database. You have to map the updated entity to the existing entity. To do so you can use AutoMapper or explicit mapping as follows:
You can solve the problem as follows:
[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody]Worker worker)
{
using (var dbContext= new MyDbContext())
{
var entityToBeUpdated = dbContext.Workers.AsNoTracking().FirstOrDefault(r => r.Id == worker.Id);
if (entity == null) return BadRequest();
entityToBeUpdated.Property1 = worker.Property1;
entityToBeUpdated.Property2 = worker.Property2;
// Do the same for the other changed properties as well
dbContext.Workers.Update(entityToBeUpdated);
dbContext.SaveChanges();
return Ok();
}
}
Alternatively you can try as follows:
[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody]Worker worker)
{
using (var dbContext= new MyDbContext())
{
var entityToBeUpdated = dbContext.Workers.FirstOrDefault(r => r.Id == worker.Id);
if (entity == null) return BadRequest();
entityToBeUpdated.Property1 = worker.Property1;
entityToBeUpdated.Property2 = worker.Property2;
// Do the same for the other changed properties as well.
dbContext.SaveChanges();
return Ok();
}
}

Related

How to insert an entity and keep the same Object Guid with EF

I need keep the same original Id (GUID) after save data because is a replication job. (SQL -> SQL remote). Then, the model can not be changed. After SaveChanges() EF insert a new random Guid as Id, then this changes my original object, and do not want that. A compact sample:
class EFInsertTest
{
public void InsertTest()
{
var id = new Guid("D75C887D-BF25-E611-943B-080027BA87E8"); // dummy
var entity = new Something { Id = id, Name = "ELENOR" };
using (var db = new SomethingContext())
{
db.Things.Add(entity);
db.SaveChanges();
// TEST
if (db.Things.Find(id) != null)
{
Console.WriteLine($"Great! Expected behavior");
}
else
{// run this:
Console.WriteLine($"Failed! Id has another value");
}
Console.ReadKey();
// SQL hard code (works fine)
db.Database.ExecuteSqlCommand($"INSERT INTO [Something] VALUES('{id}', '{entity.Name}')");
db.SaveChanges();
// TEST
if (db.Things.Find(id) != null)
{
Console.WriteLine($"Great! Expected behavior");
}
else
{
Console.WriteLine($"Failed! Id has another value");
}
Console.ReadKey();
}
}
}
public class SomethingContext : DbContext
{
public virtual DbSet<Something> Things { get; set; }
}
public class Something
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
}

Why does Entity Framework create a new entity when I try to associate an existing one?

I have a many-to-many relationship (Users to skills), and when I try to associate an existing skill to a user, it always creates a new one.
User:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public virtual ICollection<Skill> Skills { get; set; }
}
Skill:
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
}
OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasMany<Skill>(user => user.Skills)
.WithMany();
}
Creating the association:
public ActionResult Skills(SkillsViewModel viewModel)
{
var user = UserManager.FindById(User.Identity.GetUserId());
if(!string.IsNullOrWhiteSpace(viewModel.NewSkill)
&& !user.Skills
.Where(sk => sk.Name == viewModel.NewSkill)
.Any())
{
var foundSkill = this.db.Skills
.Where(sk => sk.Name == viewModel.NewSkill)
.FirstOrDefault();
if(foundSkill != null)
{
user.Skills.Add(foundSkill);
}
else
{
user.Skills.Add(new Skill()
{
Name = viewModel.NewSkill
});
}
}
if(viewModel.SelectedSkillId > 0)
{
var foundSkill = this.db.Skills.Find(viewModel.SelectedSkillId);
user.Skills.Add(foundSkill);
}
this.UserManager.Update(user);
return RedirectToAction("skills");
}
I've stepped through, and verified that I do indeed get a 'foundSkill' from the database, but after I add it to the user, and save the user, the skill associated to the user is not the one I found, but a new one with the same name and different ID.
I figured it out. The UserManager was being loaded with one DbContext, and I was trying to associate a Skill loaded from a different DbContext.
Quick hack to test and fix this was to load the user from the same DbContext as the Skills, update the user, save the DbContext.
Longer term solution would be to ensure that everything uses the same DbContext per request.

EF Context not keeping values after adding entity

Edit Is this post lacking sufficient information to get some guidance?
I have this method to insert an entity into the database:
public void Insert(T entity)
{
_context.Set<T>().Add(entity);
_context.SaveChanges();
}
When I inspect entity before adding it to the context, my CustomerRole field is there. Once the add has taken place, the context doesn't seem to have it. Because of this, I am receiving this error:
Entities in 'CcDataContext.Customers' participate in the
'Customer_CustomerRole' relationship. 0 related
'Customer_CustomerRole_Target' were found. 1
'Customer_CustomerRole_Target' is expected.
These images show what I mean:
Inspecting my entity
Inspecting the context
Can anyone explain this behaviour and what I can do about it?
This is the structure of my classes (cut down for brevity):
public class Customer : BaseEntity
{
public CustomerRole CustomerRole { get; set; }
}
class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
HasRequired(t => t.CustomerRole)
.WithMany(t => t.Customers);
}
}
public class CustomerRole : BaseEntity
{
private ICollection<Customer> _customers;
public ICollection<Customer> Customers
{
get { return _customers ?? (new List<Customer>()); }
set { _customers = value; }
}
}
I can confirm that customer map is being added to the configuration and my database is built in line with them.
This is the call I am making which does the insert:
public Customer InsertGuestCustomer()
{
var customer = new Customer();
CustomerRole guestRole = GetCustomerRoleByName("Guest");
if (guestRole == null)
throw new Exception("Customer Role is not defined!");
customer.UserName = "";
customer.EmailAddress = "";
customer.Password = "";
customer.IsAdmin = false;
customer.CustomerRole = guestRole;
_customerRepository.Insert(customer);
return customer;
}
I have no other data in my database, this would be the first customer record and only one CustomerRole. My Customer table has a Foreign Key pointing to my CustomerRole.Id table / column.
Mark your navigation properties as virtual and initialize the collection property in the entity constructor rather than from the property getter.
public class Customer : BaseEntity
{
public virtual CustomerRole CustomerRole { get; set; }
}
...
public class CustomerRole : BaseEntity
{
public CustomerRole()
{
Customers = new List<Customer>();
}
public virtual ICollection<Customer> Customers { get; protected set; }
}
In your Customers property, you were returning a new List in the getter when the backing field was null, but you never assigned this to your backing field.

Entity Framework how to add properties during save required by all entities

I'm using the entity framework and I have some properties which are common to all entities:
CreatedByUserName
CreatedDateTime
LastModifiedByUserName
LastModifiedDatetime
So currently I'm saving a new property and I'm having to go like this:
_db.ProjectApprovals.Add(projectApproval);
projectApproval.CreatedByUserName = "Dev";
projectApproval.CreatedDateTime = DateTime.Now;
_db.SaveChanges();
As you can imagine having to do:
projectApproval.CreatedByUserName = "Dev";
projectApproval.CreatedDateTime = DateTime.Now;
for all entities every save is a pain. I was thinking of wrapping the save and then I could do it in there. The problem is how would I know which entities in the context had been added or modified.
First of all you must define shared interface for all your entities implementing these properties:
public interface IEntity
{
string CreatedByUserName { get; set; }
DateTime CreatedDateTime { get; set; }
...
}
Implement this interface in your entities and override SaveChanges method in your derived ObjectContext.
public override int SaveChanges(SaveOptions saveOptions)
{
var entries = this.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
.Where(x => !x.IsRelationship && x.Entity is IEntity);
foreach (var entry in entiries)
{
var entity = entry.Entity as IEntity;
if (entry.State == EntityState.Added)
{
...
}
else
{
...
}
}
return SaveChagnes(saveOptions);
}

Entity Framework 4.1+ many-to-many relationships change tracking

How can I detect changes of ICollection<> properties (many-to-many relationships)?
public class Company
{
...
public virtual ICollection<Employee> Employees { get; set; }
}
using (DataContext context = new DataContext(Properties.Settings.Default.ConnectionString))
{
Company company = context.Companies.First();
company.Employees.Add(context.Employees.First());
context.SaveChanges();
}
public class DataContext : DbContext
{
public override int SaveChanges()
{
return base.SaveChanges();
// Company's entity state is "Unchanged" in this.ChangeTracker
}
}
Here is how to find all the changed many-to-many relationships. I've implemented the code as extension methods:
public static class IaExtensions
{
public static IEnumerable<Tuple<object, object>> GetAddedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Added, (e, i) => e.CurrentValues[i]);
}
public static IEnumerable<Tuple<object, object>> GetDeletedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Deleted, (e, i) => e.OriginalValues[i]);
}
private static IEnumerable<Tuple<object, object>> GetRelationships(
this DbContext context,
EntityState relationshipState,
Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext
.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => Tuple.Create(
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
}
Some explanation. Many-to-many relationships are represented in EF as Independent Associations, or IAs. This is because the foreign keys for the relationship are not exposed anywhere in the object model. In the database the FKs are in a join table, and this join table is hidden from the object model.
IAs are tracked in EF using "relationship entries". These are similar to the DbEntityEntry objects you get from the DbContext.Entry except that they represent a relationship between two entities rather than an entity itself. Relationship entries are not exposed in the DbContext API, so you need to drop down to ObjectContext to access them.
A new relationship entry is created when a new relationship between two entities is created, for example by adding an Employee to the Company.Employees collection. This relationship is in the Added state.
Likewise, when a relationship between two entities is removed, then the relationship entry is put into the Deleted state.
This means that to find changed many-to-many relationships (or actually any changed IA) we need to find added and deleted relationship entries. This is what the GetAddedRelationships and GetDeletedRelationships do.
Once we have relationship entries, we need to make sense of them. For this you need to know a piece of insider knowledge. The CurrentValues property of an Added (or Unchanged) relationship entry contains two values which are the EntityKey objects of the entities at either end of the relationship. Likewise, but annoyingly slightly different, the OriginalValues property of a Deleted relationship entry contains the EntityKey objects for the entities at either end of the deleted relationship.
(And, yes, this is horrible. Please don’t blame me—it is from well before my time.)
The CurrentValues/OriginalValues difference is why we pass a delegate into the GetRelationships private method.
Once we have the EntityKey objects we can use GetObjectByKey to get the actual entity instances. We return these as tuples and there you have it.
Here’s some entities, a context, and an initializer, I used to test this. (Note—testing was not extensive.)
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
public override string ToString()
{
return "Company " + Name;
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Company> Companies { get; set; }
public override string ToString()
{
return "Employee " + Name;
}
}
public class DataContext : DbContext
{
static DataContext()
{
Database.SetInitializer(new DataContextInitializer());
}
public DbSet<Company> Companies { get; set; }
public DbSet<Employee> Employees { get; set; }
public override int SaveChanges()
{
foreach (var relationship in this.GetAddedRelationships())
{
Console.WriteLine(
"Relationship added between {0} and {1}",
relationship.Item1,
relationship.Item2);
}
foreach (var relationship in this.GetDeletedRelationships())
{
Console.WriteLine(
"Relationship removed between {0} and {1}",
relationship.Item1,
relationship.Item2);
}
return base.SaveChanges();
}
}
public class DataContextInitializer : DropCreateDatabaseAlways<DataContext>
{
protected override void Seed(DataContext context)
{
var newMonics = new Company { Name = "NewMonics", Employees = new List<Employee>() };
var microsoft = new Company { Name = "Microsoft", Employees = new List<Employee>() };
var jim = new Employee { Name = "Jim" };
var arthur = new Employee { Name = "Arthur" };
var rowan = new Employee { Name = "Rowan" };
newMonics.Employees.Add(jim);
newMonics.Employees.Add(arthur);
microsoft.Employees.Add(arthur);
microsoft.Employees.Add(rowan);
context.Companies.Add(newMonics);
context.Companies.Add(microsoft);
}
}
Here’s an example of using it:
using (var context = new DataContext())
{
var microsoft = context.Companies.Single(c => c.Name == "Microsoft");
microsoft.Employees.Add(context.Employees.Single(e => e.Name == "Jim"));
var newMonics = context.Companies.Single(c => c.Name == "NewMonics");
newMonics.Employees.Remove(context.Employees.Single(e => e.Name == "Arthur"));
context.SaveChanges();
}
I cant give you the exact code for your situation, but I can tell you your situation will be simplified ten fold by having a joiner table inbetween Employees and Company just to break up the many to many relationship.