I have a DDD aggregate with User as root and Appointment as the child records. I want, when I save a User in my repository, the existing child appointments of the User are updated and the child appointment which do not exist in the database be inserted.
I have for each entity a domain class and a persistence class
I have read this post on the matter and I think I understand what the accepted answer explained, so I went with the following logic :
public async Task Update(IApplicationUserWithAppointments domainUserEntity)
{
ApplicationUserEntity persistenceUserEntity = await FindEntityById(domainUserEntity.Id);
IDictionary<Guid, AppointmentEntity> appointmentEntitiesById =
persistenceUserEntity.Appointments
.ToDictionary(appointmentEntity => appointmentEntity.Id, appointmentEntity => appointmentEntity);
persistenceUserEntity.UserName = domainUserEntity.UserName;
persistenceUserEntity.Password = domainUserEntity.Password;
persistenceUserEntity.FirstName = domainUserEntity.FirstName;
persistenceUserEntity.LastName = domainUserEntity.LastName;
persistenceUserEntity.Role = domainUserEntity.Role;
persistenceUserEntity.Validated = domainUserEntity.Validated;
persistenceUserEntity.Appointments = domainUserEntity.Appointments
.Select(appointment => BuildOrUpdateAppointmentEntity(appointmentEntitiesById, appointment))
.ToList();
this.context.Users.Update(persistenceUserEntity);
}
private static AppointmentEntity BuildOrUpdateAppointmentEntity(IDictionary<Guid, AppointmentEntity> appointmentEntitiesById,
Appointment appointment)
{
if (!appointmentEntitiesById.ContainsKey(appointment.Id))
{
return new AppointmentEntity(appointment);
}
AppointmentEntity appointmentEntity = appointmentEntitiesById[appointment.Id];
appointmentEntity.State = appointment.State.Name;
appointmentEntity.DateTime = appointment.DateTime;
return appointmentEntity;
}
The logic is that I retrieve the user entity from the database with its appointments (to avoid detached entity error). Then, I map the appointment entity, updating those which exist and creating the new one.
This logic works well for the update of existing appointment records, but for the insertion of new appointments records, the following unit test fails :
public async Task Update_ChildRecord_InsertChildRecordInDb()
{
// Given
ApplicationUserEntity entity = await this.dbDataFactory.InsertValidatedLifeAssistant();
var repository = new ApplicationUserRepository(this.context, factory);
entity.Appointments.Add(new AppointmentEntity()
{
Id = Guid.NewGuid(),
State = "Planned",
DateTime = DateTime.Today.AddDays(3)
});
// When
await repository.Update(entity.ToDomainEntity(new AppointmentStateFactory()));
await repository.Save();
// Then
entity = await this.context
.Users
.Include(u => u.Appointments)
.FirstAsync(item => item.Id == entity.Id);
(await this.context.Appointments.CountAsync()).Should().Be(1);
}
With the following error :
The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
On the Save call of the update.
I don't understand why my logic is not working. Thank you in advance
After a lot of debugging, and thank to a github post I realised that my problem was that my child record had already an Id generated, and EF Core did not expected it to. I solved my problem with by using ValueGeneratedNever() in my onModelCreating model definition. Ex :
modelBuilder.Entity<AppointmentEntity>().HasKey(appointment => appointment.Id);
modelBuilder.Entity<AppointmentEntity>().Property(appointment => appointment.Id).ValueGeneratedNever();
modelBuilder.Entity<AppointmentEntity>().Property(appointment => appointment.State);
modelBuilder.Entity<AppointmentEntity>().Property(appointment => appointment.DateTime);
I have something like this:
static Employee getEmployee(string ssn){
MyEntity context = null;
Employee employee;
try{
context = new MyEntity();
employee = context.Employee.Where(X => X.ssn.Equals(ssn));
}
catch (Exception exc){
LogLibrary.WriteLog(exc.Message);
}
finally{
if (context != null){
context.Database.Connection.Close();
}
}
return employee;
}
static Employee addEmployee(Employee emp){
MyEntity context = null;
Employee employee;
try{
context = new MyEntity();
context.Employee.Add(e);
}
catch (Exception exc){
LogLibrary.WriteLog(exc.Message);
}
finally{
if (context != null){
context.Database.Connection.Close();
}
}
}
And this is the code I want to implement:
Employee myNewEmployee = DBClass.getEmployee("12345");
myNewEmployee.name = "John";
DBClass.AddEmployee(myNewEmployee);
But I obviously receive the following exception: An entity object cannot be referenced by multiple instances of IEntityChangeTracker
So I've been recommended to do the following:
Employee myNewEmployee = DBClass.getEmployee("12345");
Employee myNewEmployee2 = new Employee();
// manually copy all the fields from myNewEmployee to myNewEmployee2
myNewEmployee2.name = "John";
DBClass.AddEmployee(myNewEmployee2);
But I think it might be unefficient due to the fact that I am wasting clock cycles to have an identical copy of the same object. Should we use a single static context for the whole application? (It's an ASPx project with master page). Where can I read more about "how to use contexts"? Thank you so much.
Since the code is working with completely separate DbContext instances for each call, it is technically possible to update the instance to copy and save it as a new employee after ensuring all unique values and keys are updated. However, your existing code is leaving the entity tracking references orphaned by disposing the DbContext. Code like that getEmployees should either be loading entities in a detached state with AsNoTracking() or detaching the instance from the DbContext before the context is disposed. The method can also be simplified to remove the finally block to handle disposal by using a using block:
static Employee getEmployee(string ssn)
{
using(var context = new MyEntity())
{
try
{
return context.Employee.AsNoTracking().Single(X => X.ssn.Equals(ssn));
}
catch (Exception exc)
{
LogLibrary.WriteLog(exc.Message);
}
}
return null;
}
Alternatively you can detach the entity before returning it:
try
{
var employee = context.Employee.Single(X => X.ssn.Equals(ssn));
context.Entry(employee).State = EntityState.Detached;
return employee;
}
using blocks take care of disposing instances within a scope. One missed finally block and you have an undisposed DbContext instance.
When that initial entity is not tracked by the now disposed DbContext, you can have code like:
var employee = getEmployee("12345");
employee.SSN = "54321";
employee.Name = "John";
using(var context = new MyEntity())
{
context.Employees.Add(employee);
context.SaveChanges();
}
Another alternative is to clone the entity.There are a few ways to do this including manually copying the values across or leveraging Automapper to copy the values across. This can be configured to ignore copying keys and unique values. At the very basic:
var config = new MapperConfiguration(cfg => cfg.CreateMap<Employee, Employee>());
var mapper = config.CreateMapper();
var sourceEmployee = getEmployee("12345");
var newEmployee = mapper.Map<Employee>(sourceEmployee);
newEmployee.SSN = "54321";
newEmployee.Name = "John";
using(var context = new MyEntity())
{
context.Employees.Add(newEmployee);
context.SaveChanges();
}
This code would need to ensure that the primary key value and any unique constraints are updated. If the employee table has an EmployeeId PK and that key is set up as an Identity then that should be covered automatically. Otherwise if the PK is something like the SSN you will need to ensure that is a new and unique value before saving. To do that you should first check the database to ensure the new SSN is unique:
using(var context = new MyEntity())
{
if (!context.Employees.Any(x => x.SSN == newEmployee.SSN))
{
context.Employees.Add(newEmployee);
context.SaveChanges();
}
else
{
// handle that the new SSN is already in the database.
}
}
Regarding just using a single static DbContext: No, that is not a good idea with EF. DbContexts by default track every instance they load unless explicitly told not to. This means the longer they are alive, the more instances they track, consuming memory and causing performance drops as EF will continually check across it's known tracked instances to see if it should return that rather than a new instance pulled from the database. It still runs the queries in most cases, so dealing with tracked instances does not save performance like you might think comparing the behaviour to caching. Normally you would want multiple calls though to be associated with a single DbContext instance so having the DbContext too finely scoped makes it less flexible. For example if you wanted to update a Position in a company and associate it with an employee, having a getEmployee() method that scoped it's own DBContext can actually have unintended consequences such as this example:
using (var context = new MyEntity())
{
var position = context.Positions.Single(x => x.PositionId == positionId);
var employee = getEmployee(ssn);
position.Employee = employee;
context.SaveChanges();
}
What this can end up resulting with is an error about a duplicate constraint on attempting to insert a new Employee, or it will insert a completely new clone of the Employee record. (If the Employee is configured with an Identity for it's PK) The reason for this is that the Position is being managed by one instance of the DbContext while the getEmployee() was using a completely separate DbContext instance. The position doesn't know that "employee" is an existing record and treats it like a brand new one. The proper way to ensure these instances are associated together is to ensure they are both associated with the same DbContext instance:
using (var context = new MyEntity())
{
var position = context.Positions.Single(x => x.PositionId == positionId);
var employee = context.Employees.Single(x => x.SSN == ssn);
position.Employee = employee;
context.SaveChanges();
}
Or else ensuring that both this code and getEmployee are injected with the same DbContext instance rather than scoping it within the methods. (I.e. dependency injection) Working with detached instances like your code is structured is possible but it can get quite messy so be cautious.
I am aware that such question has already been asked, but solution did not help me.
[Fact]
public async Task UpdateAsync()
{
string newTitle = "newTitle1";
int newBrandId = 3;
var item = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
item.BrandId = newBrandId;
item.Title = newTitle;
storeContext.Entry(item).State = EntityState.Detached;
await service.UpdateAsync(item); // exception inside
var updatedItem = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
Assert.Equal(newTitle, updatedItem.Title);
Assert.Equal(newBrandId, updatedItem.BrandId);
}
public async Task UpdateAsync(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified; // exception when trying to change the state
await _dbContext.SaveChangesAsync();
}
Message: System.InvalidOperationException : The instance of entity type 'Item' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
interesting that exception is the same even if no item retreived from db, like so
//var item = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
var item = new Item()
{
Id = 1,
BrandId = newBrandId,
CategoryId = 1,
MeasurementUnitId = 1,
StoreId = 1,
Title = newTitle
};
Had the same problem with EF core 2.2. I never experianced this with other applications.
Ended up rewriting all my update functions somehow like this:
public bool Update(Entity entity)
{
try
{
var entry = _context.Entries.First(e=>e.Id == entity.Id);
_context.Entry(entry).CurrentValues.SetValues(entity);
_context.SaveChanges();
return true;
}
catch (Exception e)
{
// handle correct exception
// log error
return false;
}
}
Alexandar's answer, which was to disable tracking completely, solved my issue, but I got worried since I didn't know what this would do to the rest of my application. So I went to the Microsoft docs and found this:
You should not disable change tracking if you want to manipulate entity instances and persist those changes to the database using SaveChanges().
This method sets the default behavior for all contexts created with these options, but you can override this behavior for a context instance using QueryTrackingBehavior or on individual queries using the AsNoTracking(IQueryable) and AsTracking(IQueryable) methods.
So the solution for me was to disable tracking only when needed. So I solved my issue by using this in the other part of my code that retrieved the same entry from the database:
var entry = await context
.SomeDbTable
.AsNoTracking() // this is what you're looking for
.Find(id);
Numerous issues I've been running into have one nasty root.
In a nutshell: I've learned the hard way why dbContext is scoped rather than singleton. Here is Store type, but the issue was the same.
Here is simplified test initialization code
public TestBase()
{
services = new ServiceCollection();
storeContext = StoreContextMock.ConfigureStoreContext(services, output);
serviceProvider = services.BuildServiceProvider();
}
public static StoreContext ConfigureStoreContext(IServiceCollection services)
{
services.AddDbContext<StoreContext>(c =>
c.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
var serviceProvider = services.BuildServiceProvider();
var storeContext = serviceProvider.GetRequiredService<StoreContext>();
storeContext .Stores.Add(new Store { Title = "John's store", Address = "NY", Description = "Electronics best deals", SellerId = "john#mail.com" });
storeContext .Stores.Add(new Store { Title = "Jennifer's store", Address = "Sydney", Description = "Fashion", SellerId = "jennifer#mail.com" });
storeContext .SaveChanges();
return storeContext ;
}
I reread error and finally noticed the main word
The instance of entity type 'Store' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked
So there has to be some orphan tracked instance preventing me from working with store. I did not save any references to s1 or s2, so it must be storeContext storing references on inserted objects even after leaving scope of their declaration and initialization. That's why I was unable update variables normally and also why my 'queried' from db objects had all their navigation properties assigned (lazy loading has little to do with this). The following code resolved all my issues.
public static StoreContext ConfigureStoreContext(IServiceCollection services)
{
services.AddDbContext<StoreContext>(c =>
c.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
var serviceProvider = services.BuildServiceProvider();
var storeContext = serviceProvider.GetRequiredService<StoreContext>();
var s1 = new Store { Title = "John's store", Address = "NY", Description = "Electronics best deals", SellerId = "john#mail.com" };
var s2 = new Store { Title = "Jennifer's store", Address = "Sydney", Description = "Fashion", SellerId = "jennifer#mail.com" }
storeContext .Stores.Add(s1);
storeContext .Stores.Add(s2);
storeContext .Entry<Store>(s1).State = EntityState.Detached;
storeContext .Entry<Store>(s2).State = EntityState.Detached;
storeContext .SaveChanges();
return storeContext ;
}
That is one of many reasons why dbContext should be limited by a scope.
Thanks for the hint.
For me was this the solution:
public void Update(int id, T obj)
{
var entry = table.Find(id);
_context.Entry(entry).CurrentValues.SetValues(obj);
}
Based on the solution Bryan gave. I think I use newer version of EF/Automapping. This works for me.
I got some similar error when I wanted to update data, and I found out I could fix it by clearing the property context. Here is what a did. It's not the same problem but it's the same error, so I think it can be fixed the same way. Clearing the context seems to be a good solution because it's the reason of whats happening.
context.ChangeTracker.Clear();
context.Cliente.Update(cliente);
context.SaveChanges();
I had same problem while I was copying some records in database by Entity Framework and changing one column that was other's entity key.
Tracking mode change did not fix the issue.
The issue was fixed by properly setting primary key in EntityTypeConfiguration, to contain the changed value here described as x.EntityTwoKey.
builder.HasKey(x => new { x.EntityOneKey, x.EntityTwoKey });
In my case I hit this error when running SaveChanges twice inside of two IFs statements. I moved the SaveChanges outside of those two blocks of code. Just a side note in my service layer it is querying the data with AsNoTracking();
if (user.SendPaymentStatus)
{
user.SendPaymentStatus = false;
saveChanges = true;
//_userService.SaveChanges(user, false);
msg = GetPaymentHTML(user.MasterNodeName, user.Payee, DbMasterNode.LastPaidUtc);
Framework.Email.SendEmail(email, "MasterNode Payment - " + user.MasterNodeName, msg);
}
if (user.SendNodeStatus)
{
user.SendNodeStatus = false;
saveChanges = true;
//_userService.SaveChanges(user, false);
msg = GetStatusHTML(user.MasterNodeName, user.Payee, DbMasterNode.CurrentStatus, DbMasterNode.LastSeenUtc);
Framework.Email.SendEmail(email, "MasterNode Down - " + user.MasterNodeName, msg);
}
if (saveChanges)
{
user.SendPaymentStatus = false;
_userService.SaveChanges(user, false);
}
I was getting the same problem when was trying to update the value. then i found the proble i was using this.
services.AddDbContext<StudentContext>(option => option.UseSqlServer(Configuration.GetConnectionString("databasename")),ServiceLifetime.Singleton);
then i remove lifetime and it worked well for me.
services.AddDbContext<StudentContext>(option => option.UseSqlServer(Configuration.GetConnectionString("databasename")));
In my case above issue was resolved after I set primary key column Id as an Identity column.
We recently run into the same issue when adding multiple new items with identity column id set to 0. We are using OracleDataAccess client for EF core 3, we set the sequence number for the new entities when we do saveChanges(), but it errors out when we try to add() if there's already another item with id=0.
The fix we did is making sure the configuration for the identity column is correct:
1.) Set the key
builder.HasKey(t => t.Id);
2.) Set the database generate option correctly
[Column("ID"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int Id { get; set; }
or fluent equivalent:
builder.Property(t => t.Id)
.ValueGeneratedOnAdd();
We did not do second step correctly and was setting as DatabaseGeneratedOption.None, then EF core failed on add.
I realise that updating entities without first selecting them is a common problem and many solutions are already on StackOverflow, however after reading these I'm still having a problem.
I'm using the following code to update a User entitiy:
using (var context = GetContext())
{
var userEntity = new UserEntity() { ID = userUpdate.ID };
context.Users.Attach(userEntity);
context.Entry(userEntity).CurrentValues.SetValues(userUpdate);
context.SaveChanges();
}
However this results in a DbEntityValidationException being thrown because my User entitiy has some required properties but these aren't necessarily set on the updated entity.
Is there any way around this or is it simply a case of removing the required properties?
Thanks!
I've found an answer here: Entity Framework/MVC3: temporarily disable validation
By temporarily disabling validation I can bypass the checks and insert any number of values without retrieving the required properties first:
using (var context = GetContext())
{
var userEntity = new UserEntity() { ID = userUpdate.ID };
context.Users.Attach(userEntity);
context.Entry(userEntity).CurrentValues.SetValues(userUpdate);
// Disable entity validation
context.Configuration.ValidateOnSaveEnabled = false;
context.SaveChanges();
}
If you only want to update particular fields in your entity without having to retrieve the entire thing from the database first:
var userEntity = new UserEntity() { ID = userUpdate.ID };
userEntity.SomeProperty = userUpdate.SomeProperty;
//Tell EF to only update the SomeProperty value:
context.Entry(userEntity).Property(x => x.SomeProperty).IsModified = true;
context.SaveChanges();
I see two codes for adding and deleting entity, I wonder which is the best way and what is the difference between these two.
One is this (for Adding):
using (var context = new BloggingContext())
{
var blog = new Blog { Name = "ADO.NET Blog" };
context.Blogs.Add(blog);
context.SaveChanges();
}
and another is this:
using (var context = new BloggingContext())
{
var blog = new Blog { Name = "ADO.NET Blog" };
context.Entry(blog).State = EntityState.Added;
context.SaveChanges();
}
and I read that calling the Add method on DbSet puts the entity into the Added state.
According to this I think above two codes are nearly same. If its not please tell me the difference.
And another code I've found is:
public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Added)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbSet.Add(entity);
}
}
and if its true that calling the Add method on DbSet puts the entity into the Added state then I think there is no difference in code in if and else block, so what's the point in here.
And from the above three code which is the best way to add an Entity.
And another code in which I have doubt is what is the use of else block in below code:
public virtual void Delete(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Deleted)
{
dbEntityEntry.State = EntityState.Deleted;
}
else
{
DbSet.Attach(entity);
DbSet.Remove(entity);
}
}
I don't see a huge benefit in setting the state of an entity to Added since creating a new entity and adding it to the set does exactly that like you mentioned. Where this type of pattern is pretty useful is when you want to delete an entity without having to fetch it from the database first:
// this entity will be unattached at this point, so if you were to call remove
// on the dbset it wouldn't do much, since it doesn't think it's in the database
var deleteThisEntity = new Blog { Id = 5 };
// if you set the state to deleted, it now thinks that it needs to be deleted
db.Entry(deleteThisEntity).State = EntityState.Deleted;
// call SaveChanges to delete
db.SaveChanges();
You can get a similar effect by setting the state to modified, so it will trigger an update statement. Sometimes you just don't want to take the extra hit of fetching an item from the database just to delete it.
ADD Patterns for ASP .NET using the code bellow is very standard practice.
using (var context = new BloggingContext())
{
var blog = new Blog { Name = "ADO.NET Blog" };
context.Blogs.Add(blog);
context.SaveChanges();
}
In the code for delete, the if/else statement is to check if the fetched object is valid.
The code I've been using for DELETE patterns is this:
var fetchedObject = context.Blog.Find(id);
if (fetchedObject == null)
{
return false;
}
else
{
ds.Blog.Remove(fetchedObject);
return true;
}
This is a method inside a Manager class that receives the id.