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);
Другие вопросы по тегам