I have a Service Object Update
public bool Update(object original, object modified)
{
var originalClient = (Client)original;
var modifiedClient = (Client)modified;
_context.Clients.Update(originalClient); //<-- throws the error
_context.SaveChanges();
//Variance checking and logging of changes between the modified and original
}
This is where I am calling this method from:
public IActionResult Update(DetailViewModel vm)
{
var originalClient = (Client)_service.GetAsNoTracking(vm.ClientId);
var modifiedClient = (Client)_service.Fetch(vm.ClientId.ToString());
// Changing the modifiedClient here
_service.Update(originalClient, modifiedClient);
}
Here is the GetAsNotTracking
method:
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}
Fetch
method:
public object Fetch(string id)
{
long fetchId;
long.TryParse(id, out fetchId);
return GetClientQueryableObject(fetchId).FirstOrDefault();
}
GetClientQueryableObject
:
private Microsoft.Data.Entity.Query.IIncludableQueryable<Client, ActivityType> GetClientQueryableObject(long searchId)
{
return _context.Clients
.Where(x => x.Id == searchId)
.Include(x => x.Opportunities)
.ThenInclude(x => x.BusinessUnit)
.Include(x => x.Opportunities)
.ThenInclude(x => x.Probability)
.Include(x => x.Industry)
.Include(x => x.Activities)
.ThenInclude(x => x.User)
.Include(x => x.Activities)
.ThenInclude(x => x.ActivityType);
}
Any ideas?
I have looked the following articles / discussions. To no avail:ASP.NET GitHub Issue 3839
UPDATE:
Here are the changes to GetAsNoTracking
:
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault();
}
GetClientQueryableObjectAsNoTracking
:
private IQueryable<Client> GetClientQueryableObjectAsNoTracking(long searchId)
{
return _context.Clients
.Where(x => x.Id == searchId)
.Include(x => x.Opportunities)
.ThenInclude(x => x.BusinessUnit)
.AsNoTracking()
.Include(x => x.Opportunities)
.ThenInclude(x => x.Probability)
.AsNoTracking()
.Include(x => x.Industry)
.AsNoTracking()
.Include(x => x.Activities)
.ThenInclude(x => x.User)
.AsNoTracking()
.Include(x => x.Activities)
.ThenInclude(x => x.ActivityType)
.AsNoTracking();
}
This question is related to
c#
asp.net
entity-framework
In my case, the table's id column was not set as an Identity column.
I got this error from my background service. I solved which creating a new scope.
using (var scope = serviceProvider.CreateScope())
{
// Process
}
public static void DetachEntity<T>(this DbContext dbContext, T entity, string propertyName) where T: class, new()
{
try
{
var dbEntity = dbContext.Find<T>(entity.GetProperty(propertyName));
if (dbEntity != null)
dbContext.Entry(dbEntity).State = EntityState.Detached;
dbContext.Entry(entity).State = EntityState.Modified;
}
catch (Exception)
{
throw;
}
}
public static object GetProperty<T>(this T entity, string propertyName) where T : class, new()
{
try
{
Type type = entity.GetType();
PropertyInfo propertyInfo = type.GetProperty(propertyName);
object value = propertyInfo.GetValue(entity);
return value;
}
catch (Exception)
{
throw;
}
}
I made this 2 extension methods, this is working really well.
Arhhh this got me and I spent a lot of time troubleshooting it. The problem was my tests were being executed in Parellel (the default with XUnit).
To make my test run sequentially I decorated each class with this attribute:
[Collection("Sequential")]
This is how I worked it out: Execute unit tests serially (rather than in parallel)
I mock up my EF In Memory context with GenFu:
private void CreateTestData(TheContext dbContext)
{
GenFu.GenFu.Configure<Employee>()
.Fill(q => q.EmployeeId, 3);
var employee = GenFu.GenFu.ListOf<Employee>(1);
var id = 1;
GenFu.GenFu.Configure<Team>()
.Fill(p => p.TeamId, () => id++).Fill(q => q.CreatedById, 3).Fill(q => q.ModifiedById, 3);
var Teams = GenFu.GenFu.ListOf<Team>(20);
dbContext.Team.AddRange(Teams);
dbContext.SaveChanges();
}
When Creating Test Data, from what I can deduct, it was alive in two scopes (once in the Employee's Tests while the Team tests were running):
public void Team_Index_should_return_valid_model()
{
using (var context = new TheContext(CreateNewContextOptions()))
{
//Arrange
CreateTestData(context);
var controller = new TeamController(context);
//Act
var actionResult = controller.Index();
//Assert
Assert.NotNull(actionResult);
Assert.True(actionResult.Result is ViewResult);
var model = ModelFromActionResult<List<Team>>((ActionResult)actionResult.Result);
Assert.Equal(20, model.Count);
}
}
Wrapping both Test Classes with this sequential collection attribute has cleared the apparent conflict.
[Collection("Sequential")]
Additional references:
https://github.com/aspnet/EntityFrameworkCore/issues/7340
EF Core 2.1 In memory DB not updating records
http://www.jerriepelser.com/blog/unit-testing-aspnet5-entityframework7-inmemory-database/
http://gunnarpeipman.com/2017/04/aspnet-core-ef-inmemory/
https://github.com/aspnet/EntityFrameworkCore/issues/12459
Preventing tracking issues when using EF Core SqlLite in Unit Tests
This error message can happen if you have duplicate entries/entities and run SaveChanges().
Cant update the DB row. I was facing the same error. Now working with following code:
_context.Entry(_SendGridSetting).CurrentValues.SetValues(vm);
await _context.SaveChangesAsync();
Without overriding EF track system, you can also Detach the 'local' entry and attach your updated entry before saving :
//
var local = _context.Set<YourEntity>()
.Local
.FirstOrDefault(entry => entry.Id.Equals(entryId));
// check if local is not null
if (local != null)
{
// detach
_context.Entry(local).State = EntityState.Detached;
}
// set Modified flag in your entry
_context.Entry(entryToUpdate).State = EntityState.Modified;
// save
_context.SaveChanges();
UPDATE: To avoid code redundancy, you can do an extension method :
public static void DetachLocal<T>(this DbContext context, T t, string entryId)
where T : class, IIdentifier
{
var local = context.Set<T>()
.Local
.FirstOrDefault(entry => entry.Id.Equals(entryId));
if (!local.IsNull())
{
context.Entry(local).State = EntityState.Detached;
}
context.Entry(t).State = EntityState.Modified;
}
My IIdentifier
interface has just an Id
string property.
Whatever your Entity, you can use this method on your context :
_context.DetachLocal(tmodel, id);
_context.SaveChanges();
I faced the same problem but the issue was very silly, By mistake I have given wrong relationship I have given relationship between 2 Ids.
public async Task<Product> GetValue(int id)
{
Product Products = await _context.Products.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
return Products;
}
AsNoTracking()
If your data has changed every once,you will notice dont tracing the table.for example some table update id ([key]) using tigger.If you tracing ,you will get same id and get the issue.
I had the same issue (EF Core) while setting up xUnit tests. What 'fixed' it for me in testing was looping through the change tracker entities after setting up the seed data.
I set up a Test Mock Context:
/// <summary>
/// Get an In memory version of the app db context with some seeded data
/// </summary>
public static AppDbContext GetAppDbContext(string dbName)
{
//set up the options to use for this dbcontext
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: dbName)
//.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
.Options;
var dbContext = new AppDbContext(options);
dbContext.SeedAppDbContext();
return dbContext;
}
Extension method to add some seed data:
foreach
loop at bottom of method. public static void SeedAppDbContext(this AppDbContext appDbContext)
{
// add companies
var c1 = new Company() { Id = 1, CompanyName = "Fake Company One", ContactPersonName = "Contact one", eMail = "[email protected]", Phone = "0123456789", AdminUserId = "" };
c1.Address = new Address() { Id = 1, AddressL1 = "Field Farm", AddressL2 = "Some Lane", City = "some city", PostalCode = "AB12 3CD" };
appDbContext.CompanyRecords.Add(c1);
var nc1 = new Company() { Id = 2, CompanyName = "Test Company 2", ContactPersonName = "Contact two", eMail = "[email protected]", Phone = "0123456789", Address = new Address() { }, AdminUserId = "" };
nc1.Address = new Address() { Id = 2, AddressL1 = "The Barn", AddressL2 = "Some Lane", City = "some city", PostalCode = "AB12 3CD" };
appDbContext.CompanyRecords.Add(nc1);
//....and so on....
//last call to commit everything to the memory db
appDbContext.SaveChanges();
//and then to detach everything
foreach (var entity in appDbContext.ChangeTracker.Entries())
{
entity.State = EntityState.Detached;
}
}
The controller put method
The .ConvertTo<>()
Method is an extension method from ServiceStack
[HttpPut]
public async Task<IActionResult> PutUpdateCompany(CompanyFullDto company)
{
if (0 == company.Id)
return BadRequest();
try
{
Company editEntity = company.ConvertTo<Company>();
//Prior to detaching an error thrown on line below (another instance with id)
var trackedEntity = _appDbContext.CompanyRecords.Update(editEntity);
await _appDbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException dbError)
{
if (!CompanyExists(company.Id))
return NotFound();
else
return BadRequest(dbError);
}
catch (Exception Error)
{
return BadRequest(Error);
}
return Ok();
}
and the test:
[Fact]
public async Task PassWhenEditingCompany()
{
var _appDbContext = AppDbContextMocker.GetAppDbContext(nameof(CompaniesController));
var _controller = new CompaniesController(null, _appDbContext);
//Arrange
const string companyName = "Fake Company One";
const string contactPerson = "Contact one";
const string newCompanyName = "New Fake Company One";
const string newContactPersonName = "New Contact Person";
//Act
var getResult = _controller.GetCompanyById(1);
var getEntity = (getResult.Result.Result as OkObjectResult).Value;
var entityDto = getEntity as CompanyFullDto;
//Assert
Assert.Equal(companyName, entityDto.CompanyName);
Assert.Equal(contactPerson, entityDto.ContactPersonName);
Assert.Equal(1, entityDto.Id);
//Arrange
Company entity = entityDto.ConvertTo<Company>();
entity.CompanyName = newCompanyName;
entity.ContactPersonName = newContactPersonName;
CompanyFullDto entityDtoUpd = entity.ConvertTo<CompanyFullDto>();
//Act
var result = await _controller.PutUpdateCompany(entityDtoUpd) as StatusCodeResult;
//Assert
Assert.True(result.StatusCode == 200);
//Act
getResult = _controller.GetCompanyById(1);
getEntity = (getResult.Result.Result as OkObjectResult).Value;
entityDto = getEntity as CompanyFullDto;
//Assert
Assert.Equal(1, entityDto.Id); // didn't add a new record
Assert.Equal(newCompanyName, entityDto.CompanyName); //updated the name
Assert.Equal(newContactPersonName, entityDto.ContactPersonName); //updated the contact
//make sure to dispose of the _appDbContext otherwise running the full test will fail.
_appDbContext.Dispose();
}
It sounds as you really just want to track the changes made to the model, not to actually keep an untracked model in memory. May I suggest an alternative approach wich will remove the problem entirely?
EF will automticallly track changes for you. How about making use of that built in logic?
Ovverride SaveChanges()
in your DbContext
.
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<Client>())
{
if (entry.State == EntityState.Modified)
{
// Get the changed values.
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
}
return base.SaveChanges();
}
Good examples can be found here:
Entity Framework 6: audit/track changes
Implementing Audit Log / Change History with MVC & Entity Framework
EDIT:
Client
can easily be changed to an interface. Let's say ITrackableEntity
. This way you can centralize the logic and automatically log all changes to all entities that implement a specific interface. The interface itself doesn't have any specific properties.
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<ITrackableClient>())
{
if (entry.State == EntityState.Modified)
{
// Same code as example above.
}
}
return base.SaveChanges();
}
Also, take a look at eranga's great suggestion to subscribe instead of actually overriding SaveChanges().
Source: Stackoverflow.com