Экземпляр типа сущности не может быть отслежен, потому что другой экземпляр этого типа с тем же ключом уже отслеживается
У меня есть служебный объект 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
}
Здесь я вызываю этот метод из:
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);
}
Вот метод GetAsNotTracking
:
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}
Fetch
метод:
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);
}
Любые идеи?
Я просмотрел следующие статьи/дискуссии. Безрезультатно: ASP.NET GitHub Issue 3839
UPDATE:
Вот изменения в 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();
}
Ответы
Ответ 1
Без переопределения системы треков EF вы также можете отсоединить "локальную" запись и прикрепить свою обновленную запись до сохранения:
//
var local = _context.Set<YourEntity>()
.Local
.FirstOrDefault(entry => entry.Id.Equals(entryId));
// check if local is not null
if (!local.IsNull()) // I'm using a extension method
{
// detach
_context.Entry(local).State = EntityState.Detached;
}
// set Modified flag in your entry
_context.Entry(entryToUpdate).State = EntityState.Modified;
// save
_context.SaveChanges();
UPDATE:
Чтобы избежать избыточности кода, вы можете использовать метод расширения:
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;
}
Мой IIdentifier
интерфейс имеет только свойство строки Id
.
Независимо от вашего объекта, вы можете использовать этот метод в своем контексте:
_context.DetachLocal(tmodel, id);
_context.SaveChanges();
Ответ 2
Звучит так, как будто вы просто хотите отслеживать изменения, внесенные в модель, а не сохранять в памяти неизведанную модель. Могу ли я предложить альтернативный подход, который полностью устранит проблему?
EF будет автоматически отслеживать изменения для вас. Как использовать встроенную логику?
Ovverride SaveChanges()
в 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();
}
Хорошие примеры можно найти здесь:
Entity Framework 6: изменения аудита/дорожки
Внедрение журнала аудита/истории изменений с помощью MVC и инфраструктуры Entity Framework
EDIT:
Client
можно легко изменить на интерфейс. Скажем ITrackableEntity
. Таким образом, вы можете централизовать логику и автоматически регистрировать все изменения для всех объектов, реализующих определенный интерфейс. Сам интерфейс не имеет каких-либо конкретных свойств.
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<ITrackableClient>())
{
if (entry.State == EntityState.Modified)
{
// Same code as example above.
}
}
return base.SaveChanges();
}
Также обратите внимание на eranga отличное предложение подписаться вместо фактического переопределения SaveChanges().
Ответ 3
В моем случае столбец идентификатора таблицы не был установлен как столбец идентификаторов.
Ответ 4
Если ваши данные менялись один раз, вы заметите, что не отслеживаете таблицу. Например, какой-то идентификатор обновления таблицы ([ключ]) использует tigger. Если вы отслеживаете, вы получите тот же идентификатор и получите проблему.
Ответ 5
Arhhh это получил меня, и я потратил много времени на устранение неполадок. Проблема заключалась в том, что мои тесты выполнялись в Parellel (по умолчанию с XUnit).
Для последовательного выполнения теста я украсил каждый класс этим атрибутом:
[Collection("Sequential")]
Вот как я работал: выполнять модульные тесты последовательно (а не параллельно)
Я копирую свой контекст EF In Memory с 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();
}
При создании тестовых данных, из того, что я могу вычесть, они были живы в двух областях (один раз в тестах сотрудников во время выполнения командных тестов):
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);
}
}
Обертывание обоих тестовых классов этим атрибутом последовательной коллекции очистило очевидный конфликт.
[Collection("Sequential")]
Дополнительные ссылки:
https://github.com/aspnet/EntityFrameworkCore/issues/7340
EF Core 2.1 В памяти БД не обновляются записи
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
Предотвращение проблем с отслеживанием при использовании EF Core SqlLite в модульных тестах
Ответ 6
public async Task<Product> GetValue(int id)
{
Product Products = await _context.Products.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
return Products;
}
AsNoTracking()