【问题标题】:ICollectionView throw Entity Framework Attach exceptionICollectionView 抛出实体框架附加异常
【发布时间】:2014-10-16 18:24:53
【问题描述】:

当我尝试将对象保存到 EF 时,它会抛出此异常:

EntityFramework.dll 中出现“System.InvalidOperationException”类型的异常,但未在用户代码中处理。

附加信息:附加类型的实体 'Sistema.DataEntities.Models.Cliente' 失败,因为另一个实体 相同的类型已经具有相同的主键值。这可能发生 当使用“附加”方法或将实体的状态设置为 “未更改”或“已修改”,如果图中的任何实体具有 键值冲突。这可能是因为一些实体是新的并且 尚未收到数据库生成的键值。在这种情况下使用 'Add' 方法或 'Added' 实体状态来跟踪图形和 然后将非新实体的状态设置为“未更改”或“已修改”为 合适。

如果我取出 '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 里面有这个:

public virtual void Update(TEntity entity)
    {
        ((IObjectState)entity).ObjectState = ObjectState.Modified;
        _dbSet.Attach(entity);// EXCEPTION HERE
        _context.SyncObjectState(entity);
    }

我正在使用Generic Unit of Work & (Extensible) Repositories Framework 对于我的存储库层。

使用Value Injecter在 ViewModel 和实体之间进行映射

还有我项目的图片(它是桌面 + 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);
        }

【问题讨论】:

  • Insert方法用于插入还是更新?第一行里面的代码似乎是一个插入操作,但是下一个代码附加到上下文中?该服务是否与客户端位于同一应用程序域中(或者该服务实际上托管在不同的应用程序域(Windows 服务/iis)上)?上下文实例是否曾经被释放/关闭?
  • 方法错误,现在正确...更新
  • 是上下文实例单例(只要域存在,就只有一个)?它曾经关闭/处置吗?
  • 是的......如何管理它是 UnityOfWork 的统一。这些处置在我使用的存储库框架内得到了很好的管理。此错误可能与此存储库框架有关吗? public virtual void Update(TEntity entity) 来自这个框架。

标签: c# entity-framework mvvm prism valueinjecter


【解决方案1】:

我不太清楚上下文/存储库/服务的创建方式,如果在保存更改后正确处理上下文并在每次新操作时创建一个新上下文,这应该不是问题,因为Local缓存总是新的。

并且异常说有一个具有相同Id的现有实体已经附加到Local缓存,你不能附加另一个具有相同Id的实体,你需要先分离现有实体。

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;
}

更新2

异常也可能是由于从DbSet&lt;T&gt; 检索项目然后附加具有相同键的另一个不同实体,默认情况下将跟踪(附加)这些项目。可以通过提及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);

【讨论】:

  • 我与 Generic Unit of Work & (Extensible) Repositories Framework 的创建者进行了交谈,他告诉我该问题已在 1 周前得到解决……我将下载下一个版本进行测试。感谢您的帮助。
  • @Daniloloko,在他们的repository test example 中,每个操作都会实例化上下文和工作单元(短期上下文),如果您想要长期上下文,则需要按照我的解释进行操作之前,我添加了另一种选择,方法是覆盖保存更改以在保存更改后分离修改的实体。
  • 我不认为这是问题所在,因为每次都没有清理本地...我正在调试它...请参阅有关如何重现它的更新。
  • @Daniloloko,你更新的代码是由_clienteService.ClienteService_GetAll();检索Cliente时被跟踪的实体引起的,可以简化为this example,可以通过引入AsNoTracking来解决,并且由于框架包装了DbSet&lt;T&gt;,所以你不能使用它,我认为这个框架不适合windows应用程序,除非你改变你实例化IClienteService到每个操作(每个方法调用)的方式跨度>
  • 感谢您的回信...您帮了我很多...我认为这个框架确实不适合 Windows 应用程序...我正在尝试与框架所有者交谈.. .但是返回消息需要时间。要添加AsNoTracking...我需要更新存储库框架...对吗?这就是问题...
猜你喜欢
  • 2012-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 2016-10-08
  • 1970-01-01
相关资源
最近更新 更多