ICollectionView выдает исключение Entity Framework Attach
Когда я пытаюсь сохранить объект в EF, выдается это исключение:
Исключение типа "System.InvalidOperationException" произошло в EntityFramework.dll, но не было обработано в коде пользователя.
Дополнительная информация: Не удалось подключить объект типа 'Sistema.DataEntities.Models.Cliente', поскольку другой объект того же типа уже имеет такое же значение первичного ключа. Это может произойти при использовании метода "Присоединить" или установке состояния объекта на "Неизменен" или "Изменен", если какие-либо объекты в графе имеют конфликтующие значения ключей. Это может быть потому, что некоторые объекты являются новыми и еще не получили сгенерированные базой данных значения ключей. В этом случае используйте метод "Добавить" или "Добавленный" объект сущности, чтобы отслеживать график, а затем установите состояние не новых объектов в "Неизмененный" или "Измененный", в зависимости от ситуации.
Если я достану "cliItems = new ListCollectionView(t.ToList());" он работает отлично, но мне нужно использовать ListCollectionView для шаблонов PRISM.
public class CadastroClienteViewModel : BindableBase, ICadastroClienteViewModel
{
private readonly IClienteService _clienteService;
public CadastroClienteViewModel(IClienteService ServiceCliente)
{
_clienteService = ServiceCliente;
this.SaveCommand = new DelegateCommand(ExecuteMethodSave);
this.RefreshCommand = new DelegateCommand(ExecuteMethodRefresh, CanExecuteMethodRefresh);
RefreshCommand.Execute(null);
}
private void ExecuteMethodSave()
{
Sistema.DataEntities.Models.Cliente clifinal = new Sistema.DataEntities.Models.Cliente();
clifinal.InjectFrom<UnflatLoopValueInjection>(ObCliente);
_clienteService.ClienteService_Update(clifinal); //EXCEPTION HERE
RefreshCommand.Execute(null);
}
private bool CanExecuteMethodRefresh()
{
return true;
}
private void ExecuteMethodRefresh()
{
//var t = _clienteService.ClienteService_GetAll().ToList();
//var y = t.Select(p => new Cliente().InjectFrom<FlatLoopValueInjection>(p));
var t = _clienteService.ClienteService_GetAll().Select(p => new Cliente().InjectFrom<FlatLoopValueInjection>(p));
cliItems = new ListCollectionView(t.ToList());//if i take this line out, no exceptions.
cliItems.CurrentChanged += cliItemsOnCurrentChanged;
OnPropertyChanged("cliItems");
}
private void cliItemsOnCurrentChanged(object sender, EventArgs eventArgs)
{
ObCliente = (Cliente)cliItems.CurrentItem;
this.OnPropertyChanged("ObCliente");
}
public ICommand SaveCommand { get; private set; }
public ICommand RefreshCommand { get; private set; }
public Cliente ObCliente { get; private set; }
public ICollectionView cliItems { get; private set; }
}
Мой Сервис (Бизнес Логика) Класс:
public class ClienteService : Common.Services.Service<Cliente>, IClienteService
{
private readonly IRepositoryAsync<Cliente> _repository;
private readonly IUnitOfWorkAsync _uow;
public ClienteService(IRepositoryAsync<Cliente> repository, IUnitOfWorkAsync uow)
: base(repository)
{
_repository = repository;
_uow = uow;
}
public void ClienteService_Adicionar(Cliente Obcliente)
{
_repository.Insert(Obcliente);
_uow.SaveChanges();
}
public void ClienteService_Update(Cliente Obcliente)
{
Obcliente.ObjectState = ObjectState.Modified;
_repository.Update(Obcliente);//HERE THE EXCEPTION
_uow.SaveChanges();
}
public IEnumerable<Cliente> ClienteService_GetAll()
{
var t = _repository.Query().Select().AsEnumerable();
return t;
}
}
Внутри моего Repository.cs o есть это:
public virtual void Update(TEntity entity)
{
((IObjectState)entity).ObjectState = ObjectState.Modified;
_dbSet.Attach(entity);// EXCEPTION HERE
_context.SyncObjectState(entity);
}
Я использую универсальную структуру работы и (расширяемую) структуру репозитория. Для моего уровня репозитория.
Для отображения между ViewModels и Entity im с использованием Value Injecter
И изображение моего проекта (это рабочий стол + модули UNITY + Prism)
ОБНОВИТЬ:
Как воспроизвести это:
IEnumerable<Cliente> Clientes = _clienteService.ClienteService_GetAll();
var personViewModels = new List<Sistema.MVVMModels.CadastroModule.Cliente>().InjectFrom(Clientes);
Sistema.MVVMModels.CadastroModule.Cliente cliConvertido = personViewModels.SingleOrDefault(x => x.ClienteID == 1);
//cliConvertido.InjectFrom<SmartConventionInjection>(obCliente);
cliConvertido.Nome = "A" + rand.Next(999999, 9999999) + " BREDA";
Cliente obCliente = new Cliente();
obCliente.InjectFrom<SmartConventionInjection>(cliConvertido);
_clienteService.ClienteService_Update(obCliente);
ОБНОВЛЕНИЕ РЕШЕНО:
Проблема решена с помощью комментария к ответу выше.
Repository.cs имеет внутренний IQueryable Select(.... Я добавил AsNoTracking() в этой строке:
IQueryable<TEntity> query = _dbSet.AsNoTracking();
Теперь, когда я обновляю свой объект, используя:
public virtual void Update(TEntity entity)
{
var existing = _dbSet.Local;// NOW LOCAL IS NULL
entity.ObjectState = ObjectState.Modified;
_dbSet.Attach(entity);//no exceptions here
_context.SyncObjectState(entity);
}
1 ответ
Я не совсем понимаю, как создается контекст / хранилище / служба, если контекст удаляется должным образом после сохранения изменений и создает новый при каждой новой операции, это не должно быть проблемой, так как Local
кеш всегда новый.
И исключение говорит, что существует существующий объект с тем же Id, который был присоединен к Local
кеш, вы не можете присоединить другую сущность с тем же идентификатором, вам нужно сначала отсоединить существующую сущность.
var existing = _dbSet.Local.FirstOrDefault(x => x.Id == entity.Id);
if (existing != null)
_context.Entry(existing).State = EntityState.Detached;
_dbSet.Attach(entity);// EXCEPTION HERE
Обновить
Другой альтернативой будет переопределение SaveChanges
и отсоедините измененные объекты после их сохранения.
public override int SaveChanges()
{
var modifiedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified).ToArray();
var rowsAffected = base.SaveChanges();
foreach (var entity in modifiedEntities)
entity.State = EntityState.Detached;
return rowsAffected;
}
Update2
Исключение также может быть вызвано извлечением элементов из DbSet<T>
затем прикрепите другой объект с тем же ключом, по умолчанию эти элементы будут отслеживаться (прикрепляться). Это можно отключить, упомянув AsNoTracking
,
Вот простая ошибка, которая вызывает ошибку при получении элементов.
Entity item = new Entity { Id = 324 };
// itemDb is automatically attached.
var itemDb = db.Set<Entity>().First(x => x.Id == 324);
// Attaching another different entity (different reference)
// with the same key will throw exception.
db.Set<Entity>().Attach(entity);
Если не AsNoTracking
указан.
var itemDb = db.Set<Entity>().AsNoTracking().First(x => x.Id == 324);