出处:http://www.cnblogs.com/scy251147/p/3688844.html

关于Entity Framework中的Attached报错的完美解决方案终极版

前发表过一篇文章题为《关于Entity Framework中的Attached报错的完美解决方案》,那篇文章确实能解决单个实体在进行更新、删除时Attached的报错,注意我这里说的单个实体,指的是要更新或删除的实体不包含其它实体(比如导航属性就包含其它实体),也就是简单POCO对象;但如果不是呢?那么那篇文章里的方法在一定程度上不起作用了,仍会报错,我开始也想不明白,明明通过IsAttached函数判断要更新的实体并未Attached,但进行Attaching时但仍然报错说有相同Key,开始还以为是MS的BUG,后经过多次反复调试发现,报错是对的,因为他报的错并不是我当前要更新的实体,而是该实体中关联的实体,代码与演示报错如下:(仅是演示代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class A
{
   public string a{get;set;}
   public string b{get;set;}
   public string c{get;set;}
   public virtual B b{get;set;}
}
 
public class B
{
   public string x{get;set;}
   public string y{get;set;}
   public string z{get;set;}
}
 
var a1= dbContext.Set<A>().Single();
a1.a="test1";
dbContext.SaveChanges();
 
dbContext.Detach(a1);//从缓存中移除a1实体;
 
 
var a2= dbContext.Set<A>().AsNoTracking().Single();
a2.a="test2";
 dbContext.Set<A>().Attach(a2); //报错,说B相同的KEY已经有Attached
dbContext.Entry(entity).State = EntityState.Modified;
dbContext.SaveChanges();

针对这个报错,我在想,为何查询实体A的时候能同时关联查询实体B并都同时Attached到内存中,而当我执行Detach实体A时,却没能关联Detach实体B,问题根源就在这里,知道这个原因了,现在就是要解决这个问题,如何解决呢?既然知道是Detach实体不全面造成的,那么我只需要获取到当前DbContext上下文对象中现有的所有已Attached实体,在执行完相应的CRUD时,再全部依次Detach掉即可,解决方案代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// <summary>
/// 清空DB上下文中所有缓存的实体对象
/// </summary>
private void DetachedAllEntities()
{
    var objectContext = ((IObjectContextAdapter)this.baseContext).ObjectContext;
    List<ObjectStateEntry> entries = new List<ObjectStateEntry>();
    var states = new[] { EntityState.Added, EntityState.Deleted, EntityState.Modified, EntityState.Unchanged };
    foreach (var state in states)
    {
        entries.AddRange(objectContext.ObjectStateManager.GetObjectStateEntries(state));
    }
 
    foreach (var item in entries)
    {
        objectContext.Detach(item.Entity);
    }
}
 
 
public void Commit()  //封装的统一提交方法
{
    this.baseContext.SaveChanges();
    this.DetachedAllEntities();//执行清除
}

在使用的时候配合之前那篇文章的IsAttached函数就能完美解决所有的Attached报错问题了!

=======================================================================================

在Repository模式中,我的Update方法总是无法更新实体,这个非常郁闷,Update方法如下:

   1:  public virtual void Update(T entity)
   2:          {
   3:              try
   4:              {
   5:                  if (entity == null) throw new ArgumentNullException("实体类为空");
   6:                  Context.Entry(entity).State = EntityState.Modified;
   7:                  //Context.SaveChanges();
   8:              }
   9:              catch (DbEntityValidationException dbex)
  10:              {
  11:                  var msg = string.Empty;
  12:                  foreach (var validationErrors in dbex.EntityValidationErrors)
  13:                      foreach (var validateionError in validationErrors.ValidationErrors)
  14:                          msg += string.Format("Property:{0} Error:{1}", validateionError.PropertyName, validateionError.ErrorMessage);
  15:   
  16:                  var fail = new Exception(msg, dbex);
  17:                  throw fail;
  18:              }
  19:          }

看上去是没有任何问题的代码,一旦有实体更新的时候,总会出现如下的错误提示:

Attaching an entity of type 'TinyFrame.Data.DomainModel.t_user_application' failed because another entity of the same type already has the same primary keyvalue. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

看字面意思,好像是我的EntityState设置不正确导致的,虽然我尝试过重新设置几次EntityState,但是仍旧无法解决我的问题。

然后实在找不出原因,就利用关键字 “ EF Repository Update ”在Google上面搜集,果然找到一篇文章:Advanced Entity Framework 6 Scenarios for an MVC 5 Web Application (12 of 12),其中有一段话,提出了问题的所在:

This happened because of the following sequence of events:
 
    The Edit method calls the ValidateOneAdministratorAssignmentPerInstructor method, which retrieves all departments that have Kim Abercrombie as their administrator. That causes the English department to be read. As a result of this read operation, the English department entity that was read from the database is now being tracked by the database context.
    The Edit method tries to set the Modified flag on the English department entity created by the MVC model binder, which implicitly causes the context to try to attach that entity. But the context can't attach the entry created by the model binder because the context is already tracking an entity for the English department.
 
One solution to this problem is to keep the context from tracking in-memory department entities retrieved by the validation query. There's no disadvantage to doing this, because you won't be updating this entity or reading it again in a way that would benefit from it being cached in memory.
 

问题的原因如下:

在Context对象中,已经hold住了一个需要操作的对象,当我们把EntityState修改成modified的时候,Context会再次去加载那个操作对象,但是这样加载是无法成功的,因为当前已经存在一个对象了,再加载会导致重复,然后抛出失败的错误。

解决方法很简单,就是在展示列表的时候,利用AsNoTracking将Hold住的对象释放掉即可。我们修改代码如下:

 
 
   1:   public virtual T Get(Expression<Func<T, bool>> where)
   2:          {
   3:              return Dbset.Where(where).AsNoTracking().FirstOrDefault<T>();
   4:          }
   5:   
   6:          public virtual IQueryable<T> GetMany(Expression<Func<T, bool>> where)
   7:          {
   8:              return Dbset.Where(where).AsNoTracking();
   9:          }

然后提交,OK,问题解决。

=============================Update 2014.09.19======================

看到评论中有朋友虽然按照上述方法,但是仍然无法解决这一问题。原因是在Context中还保留有当前实体的副本所致,这里只要我们将实体副本从内存中完全移除,就可以了。

   //用于监测Context中的Entity是否存在,如果存在,将其Detach,防止出现问题。
private Boolean RemoveHoldingEntityInContext(T entity)
{
var objContext = ((IObjectContextAdapter)_context).ObjectContext;
var objSet = objContext.CreateObjectSet<T>();
var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entity); Object foundEntity;
var exists = objContext.TryGetObjectByKey(entityKey, out foundEntity); if (exists)
{
objContext.Detach(foundEntity);
} return (exists);
}

然后在Repository中,在进行更新和删除之前,运行一下即可:

  public T Remove(T entity)
{
try
{
RemoveHoldingEntityInContext(entity); _context.DbSet<T>().Attach(entity);
return _context.DbSet<T>().Remove(entity);
}
catch (DbEntityValidationException dbex)
{
var msg = string.Empty;
foreach (var validationErrors in dbex.EntityValidationErrors)
foreach (var validateionError in validationErrors.ValidationErrors)
msg += string.Format("属性:{0} 错误:{1}", validateionError.PropertyName, validateionError.ErrorMessage);
var fail = new Exception(msg, dbex);
throw fail;
}
} public T Update(T entity)
{
try
{
RemoveHoldingEntityInContext(entity); var updated = _context.DbSet<T>().Attach(entity);
_context.DbContext.Entry(entity).State = EntityState.Modified;
return updated;
}
catch (DbEntityValidationException dbex)
{
var msg = string.Empty;
foreach (var validationErrors in dbex.EntityValidationErrors)
foreach (var validateionError in validationErrors.ValidationErrors)
msg += string.Format("属性:{0} 错误:{1}", validateionError.PropertyName, validateionError.ErrorMessage);
var fail = new Exception(msg, dbex);
throw fail;
}
}
 
参考博文:http://bbs.csdn.net/topics/391074562
 

Repository模式中,Update总是失败及其解析(转)的更多相关文章

  1. Repository模式中,Update总是失败及其解析

    在Repository模式中,我的Update方法总是无法更新实体,这个非常郁闷,Update方法如下: 1: public virtual void Update(T entity) 2: { 3: ...

  2. 使用Repository模式构建数据库访问层

    使用Repository模式构建数据库访问层 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程[二]——使用Repository模式构建数据库访问层 系列导航地址http:// ...

  3. 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【二】——使用Repository模式构建数据库访问层

    系列导航地址http://www.cnblogs.com/fzrain/p/3490137.html 前言 在数据访问层应用Repository模式来隔离对领域对象的细节操作是很有意义的.它位于映射层 ...

  4. Entity Framework Repository模式

    Repository模式之前 如果我们用最原始的EF进行设计对每个实体类的“C(增加).R(读取).U(修改).D(删除)”这四个操作. 第一个:先来看看查询,对于实体类简单的查询操作,每次都是这样的 ...

  5. (转)MVC中的Repository模式

    1.首先创建一个空的MVC3应用程序,命名为MyRepository.Web,解决方案命名为MyRepository. 2.添加一个类库项目,命名为MyRepository.DAL,添加一个文件夹命名 ...

  6. MVC中的Repository模式

    1.首先创建一个空的MVC3应用程序,命名为MyRepository.Web,解决方案命名为MyRepository. 2.添加一个类库项目,命名为MyRepository.DAL,添加一个文件夹命名 ...

  7. 在 Laravel 5 中使用 Repository 模式实现业务逻辑和数据访问的分离

    1.概述 首先需要声明的是设计模式和使用的框架以及语言是无关的,关键是要理解设计模式背后的原则,这样才能不管你用的是什么技术,都能够在实践中实现相应的设计模式. 按照最初提出者的介绍,Reposito ...

  8. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

  9. MVC架构中的Repository模式 个人理解

    关于MVC架构中的Repository模式   个人理解:Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间.它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接 ...

随机推荐

  1. AS3 歌词同步

    这里实例素材: 我们不一样.lrc 我们不一样.mp3 歌词同步其实就是靠lrc文本文件,打开它,可以看到时间点和对应的歌词. 打开lrc内容如下: [ti:我们不一样][ar:大壮][al:][by ...

  2. 运行tomcat显示指定的服务未安装解决办法

    一.问题重现 二.原因分析 tomcat7.exe和tomcat7w.exe要起作用必须先未这两个文件安装服务. 其中tomcat7.exe这个文件是用来启动tomcat的,tomcat7w.exe这 ...

  3. WP8.1 发送邮件

    Method 1: Windows.System.Launcher.LaunchUriAsync(new Uri("abc@outlook.com?subject=hello world&a ...

  4. ubuntu sudo apt-get upgrade 和 sudo apt-get dist-upgrade区别

    sudo apt-get upgrade: 不会对系统产生重大的影响,可以在任何时候运行. sudo apt-get dist-upgrade: 涉及核心的升级,通常会对系统功能产生实际的影响,可能在 ...

  5. linux 中特殊符号用法详解

    # 井号 (comments)#管理员  $普通用户 脚本中 #!/bin/bash   #!/bin/sh井号也常出现在一行的开头,或者位于完整指令之后,这类情况表示符号后面的是注解文字,不会被执行 ...

  6. 类定义,创建/销毁OC对象

    类定义 1 OC类分为2个文件,一个是.h文件,一个是.m文件: 2 .h文件存放类.函数声明: 3 .m文件类的具体实现: 4 类声明使用关键字@interface.@end来声明: 5 类实现使用 ...

  7. Java Magic. Part 2: 0xCAFEBABE

    Java Magic. Part 2: 0xCAFEBABE @(Base)[JDK, magic, 黑魔法] 转载请写明:原文地址 英文原文 系列文章: -Java Magic. Part 1: j ...

  8. HTML meta 文本 格式排版 链接图表 列表 表单 frame后台布局实例

    meta标签 content属性必须和http-equiv或者name属性一起使用 http-equiv属性,就是http当量,用于和服务器发送数据前的提交交互使用.(另层含义这个当量在浏览器和服务器 ...

  9. java mysql大数据量批量插入与流式读取分析

    总结下这周帮助客户解决报表生成操作的mysql 驱动的使用上的一些问题,与解决方案.由于生成报表逻辑要从数据库读取大量数据并在内存中加工处理后在 生成大量的汇总数据然后写入到数据库.基本流程是 读取- ...

  10. oracle 网络环境配置

    PLSQL Developer连接Oracle11g 64位数据库配置详解 最近换了台64bit的电脑,所以oracle数据库也跟着换成了64bit的,不过 问题也随之产生,由于plsql devel ...