关于Entity Framework更新的几种方式以及可能遇到的问题(附加类型“Model”的实体失败,因为相同类型的其他实体已具有相同的主键值)在使用 "Attach" 方法或者将实体的状态设置为 "Unchanged" 或 "Modified" 时如果图形中的任何实体具有冲突键值,则可能会发生上述行为
在日常使用Entity Framework中,数据更新通常会用到。下面就简单封装了一个DBContext类
public partial class EFContext<T> : DbContext where T : class { public EFContext(): base("name=MyConnectionString") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { Database.SetInitializer<EFContext<T>> (null); modelBuilder.Configurations.Add(new MemberMap()); modelBuilder.Configurations.Add(new RoleMap()); base.OnModelCreating(modelBuilder); } public DbSet<T> Table { get; set; } public IQueryable<T> GetList(Expression<Func<T,bool>> where) { return this.Table.Where(where); } public void Update(T entity) { if (entity == null) { throw new ArgumentException("entity"); } this.SaveChanges(); } }
第一种更新方式,先通过Entity Framework从数据库中查找出一条记录(实体对象),然后修改实体对象的各个属性,最后调用Update方法
static void Main(string[] args) { EFContext<Member> memberContext = new EFContext<Member>(); var members = memberContext.GetList(m => true).ToList(); var model = members.Find(m => m.Id == 3); //第一种更新方式 model.Name = "猪八戒"; model.Delete = false; memberContext.Update(model); Console.ReadKey(); }
运行程序前:
运行程序后:
上面的方式可以修改为下面方式,DbContext封装类中Update可以修改为如下的形式:
public void Update(T entity) { if (entity == null) { throw new ArgumentException("entity"); } this.Table.Attach(entity); this.Entry(entity).State = EntityState.Modified; this.SaveChanges(); }
static void Main(string[] args) { EFContext<Member> memberContext = new EFContext<Member>(); var members = memberContext.GetList(m => true).ToList(); var model = members.Find(m => m.Id == 3); //第一种更新方式 model.Name = "沙师弟"; model.Delete = true; memberContext.Update(model); Console.ReadKey(); }
运行前:
运行后:
第二种方式是new一个对象,这个对象各个属性赋值,主键Id与数据库某条已存在的记录的Id相同
static void Main(string[] args) { EFContext<Member> memberContext = new EFContext<Member>(); var members = memberContext.GetList(m => true).ToList(); //var model = members.Find(m => m.Id == 3); ////第一种更新方式 //model.Name = "沙师弟"; //model.Delete = true; //memberContext.Update(model); //第二种方式 Member entity = new Member(); entity.Id = 3; entity.Name = "李小龙"; entity.Password = "lixiaolong"; entity.Delete = false; entity.RoleId = 3; memberContext.Update(entity); Console.WriteLine("update complete."); Console.ReadKey(); }
运行程序,会抛出异常。
附加类型“Core.Member”的实体失败,因为相同类型的其他实体已具有相同的主键值。在使用 "Attach" 方法或者将实体的状态设置为 "Unchanged" 或 "Modified" 时如果图形中的任何实体具有冲突键值,则可能会发生上述行为。这可能是因为某些实体是新的并且尚未接收数据库生成的键值。在此情况下,使用 "Add" 方法或者 "Added" 实体状态跟踪该图形,然后将非新实体的状态相应设置为 "Unchanged" 或 "Modified"。
因为Attach的实体对象是通过new创建的,而不是通过Entity Framework从数据库中获取的,但实例的主键对应数据在数据库中存在,该实例而不存在于DBContext上下文中,尝试Attach会抛出异常。通过监视可以看到是未附加到DbContext中
修改Update方法如下:
public void Update(T entity) { if (entity == null) { throw new ArgumentException("entity"); } if (this.Entry(entity).State == EntityState.Detached) { HandleDetached(entity); } this.Table.Attach(entity); this.Entry(entity).State = EntityState.Modified; this.SaveChanges(); } private bool HandleDetached(T entity) { var objectContext = ((IObjectContextAdapter)this).ObjectContext; var entitySet = objectContext.CreateObjectSet<T>(); var entityKey = objectContext.CreateEntityKey(entitySet.EntitySet.Name, entity); object foundSet; bool exists = objectContext.TryGetObjectByKey(entityKey, out foundSet); if (exists) { objectContext.Detach(foundSet); //从上下文中移除 } return exists; }
再次运行程序,没有问题