【问题标题】:How to update record using Entity Framework 6?如何使用 Entity Framework 6 更新记录?
【发布时间】:2014-11-11 17:26:42
【问题描述】:

我正在尝试使用 EF6 更新记录。首先找到记录,如果存在,更新。 这是我的代码:

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

每次我尝试使用上述代码更新记录时,都会收到此错误:

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException:存储 更新、插入或删除语句影响了意外数量的 行 (0)。实体可能已被修改或删除,因为实体 被加载。刷新 ObjectStateManager 条目

【问题讨论】:

  • 旁注:catch (Exception ex){throw;} 是多余的,您可以完全删除它。
  • try catch 块只是为了找出失败的原因。但是仍然不明白为什么这段代码会失败?
  • 我不是这个主题的专家,我无法回答这个问题。但是如果没有 try catch 也可以在出现异常时使用break when exception is thrown feature 来中断调试器。
  • 你没有改变任何东西。使用实体状态不会改变对象实际上没有被修改的事实。
  • 好吧,我做了和你一样的事情,没有得到错误。异常说 DbUpdateConcurrencyException。你是如何处理并发的?您是否使用了时间戳,您是否克隆然后再次合并对象,或者您是否使用了自我跟踪实体? (3 种最常用的方法)。如果你没有处理并发,我想这就是问题所在。

标签: c# entity-framework entity-framework-6 ef-database-first


【解决方案1】:

您正在尝试更新记录(对我而言,这意味着“更改现有记录的值并将其保存回来”)。因此,您需要检索对象、进行更改并保存它。

using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        result.SomeValue = "Some new value";
        db.SaveChanges();
    }
}

【讨论】:

  • 赋值不会更新数据库,调用db.SaveChanges()在上下文中修改对象会更新数据库。
  • 它仍然让我着迷......所以 var 结果,实际上连接到 dbcontext... 所以这意味着由任何 dbcontext 成员实例化的任何变量实际上都会与数据库相关联,所以无论对该变量应用什么更改,它也会被应用或持久化?
  • 因为上下文生成了对象,上下文可以跟踪对象,包括对象的变化。当您调用 SaveChanges 时,上下文会评估它正在跟踪的所有对象,以确定它们是否被添加、更改或删除,并向连接的数据库发出适当的 SQL。
  • iam 面临同样的问题 - 使用 EF6 ,尝试更新实体。附加 + EntityState.Modified 不起作用。唯一有效的是 - 您需要检索对象,进行所需的更改,并通过 db.SaveChanges(); 保存它
  • 您不必先检索对象才能对其进行更新。在我意识到我正在尝试更改其中一个主键值(复合键)之前,我遇到了同样的问题。只要您提供正确的主键,您就可以将 EntityState 设置为 Modified 并且 SaveChanges() 将起作用,前提是您不破坏表上定义的其他一些完整性约束。
【解决方案2】:

我一直在查看 Entity Framework 的源代码,如果您知道 Key 属性,则找到了一种实际更新实体的方法:

public void Update<T>(T item) where T: Entity
{
    // assume Entity base class have an Id property for all items
    var entity = _collection.Find(item.Id);
    if (entity == null)
    {
        return;
    }

    _context.Entry(entity).CurrentValues.SetValues(item);
}

否则,请检查 AddOrUpdate 实现以获取想法。

希望对您有所帮助!

【讨论】:

  • 不错!无需枚举所有属性。我假设设置值后需要SaveChanges() 调用。
  • 是的,更改将保留在 SaveChanges() 上
  • 很好的答案,IntelliSense 不太清楚做这样的事情是行不通的:_context.MyObj = newObj;然后 SaveChanges() 或.... _context.MyObj.Update(newObj) 然后 SaveChanges();您的解决方案无需遍历所有属性即可更新整个对象。
  • 这向我抱怨我正在尝试编辑 ID 字段
  • @VasilyHall - 如果模型之间的 ID 字段(或您定义的主键)不同(包括其中一个模型中的 null / 0),则会发生这种情况。确保两个模型之间的 ID 匹配,它会更新得很好。
【解决方案3】:

你可以使用AddOrUpdate方法:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();

【讨论】:

  • IMO 最佳解决方案
  • .AddOrUpdate() 用于数据库迁移期间,强烈建议不要在迁移之外使用此方法,因此它位于Entity.Migrations 命名空间中。
  • 正如@AdamVincent 所说,AddOrUpdate() 方法用于迁移,不适合只需要更新现有行的情况。如果您没有带有搜索参考(即 ID)的书,它将创建新行,并且在某些情况下可能会出现问题(例如,如果您有一个 API 需要返回 404-NotFound 响应尝试为不存在的行调用 PUT 方法)。
  • 除非你知道自己在做什么,否则不要使用它!!!!!!!!!!!!!!!!阅读:michaelgmccarthy.com/2016/08/24/…
  • 我今天又回到了这个问题,我可以警告大家,这不是所需用例的好解决方案
【解决方案4】:

所以你有一个更新的实体,你想用最少的代码在数据库中更新它......

并发总是很棘手,但我假设您只是希望您的更新获胜。以下是我对同一案例的做法,并修改了名称以模仿您的课程。换句话说,只需将attach 更改为add,它对我有用:

public static void SaveBook(Model.Book myBook)
{
    using (var ctx = new BookDBContext())
    {
        ctx.Books.Add(myBook);
        ctx.Entry(myBook).State = System.Data.Entity.EntityState.Modified;
        ctx.SaveChanges();
    }
}

【讨论】:

    【解决方案5】:

    如果您想更新对象中的所有字段,您应该使用 Entry() 方法。 另请记住,您无法更改字段 ID(键),因此首先将 ID 设置为与您编辑时相同。

    using(var context = new ...())
    {
        var EditedObj = context
            .Obj
            .Where(x => x. ....)
            .First();
    
        NewObj.Id = EditedObj.Id; //This is important when we first create an object (NewObj), in which the default Id = 0. We can not change an existing key.
    
        context.Entry(EditedObj).CurrentValues.SetValues(NewObj);
    
        context.SaveChanges();
    }
    

    【讨论】:

    • 您至少应该尝试回答问题,而不仅仅是发布代码
    • 请对问题做一些解释,而不是只留下代码sn-p,以便更好地帮助提问者。
    【解决方案6】:

    Attach一个实体会将其跟踪状态设置为Unchanged。要更新现有实体,您只需将跟踪状态设置为Modified。根据EF6 docs

    如果您知道某个实体已经存在于数据库中但可能已对其进行了更改,那么您可以告诉上下文附加该实体并将其状态设置为已修改。例如:

    var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };
    
    using (var context = new BloggingContext())
    {
        context.Entry(existingBlog).State = EntityState.Modified;
    
        // Do some more work...  
    
        context.SaveChanges();
    }
    

    【讨论】:

      【解决方案7】:

      此代码是仅更新一组列而不进行查询以首先返回记录的测试结果。它首先使用 Entity Framework 7 代码。

      // This function receives an object type that can be a view model or an anonymous 
      // object with the properties you want to change. 
      // This is part of a repository for a Contacts object.
      
      public int Update(object entity)
      {
          var entityProperties =  entity.GetType().GetProperties();   
          Contacts con = ToType(entity, typeof(Contacts)) as Contacts;
      
          if (con != null)
          {
              _context.Entry(con).State = EntityState.Modified;
              _context.Contacts.Attach(con);
      
              foreach (var ep in entityProperties)
              {
                  // If the property is named Id, don't add it in the update. 
                  // It can be refactored to look in the annotations for a key 
                  // or any part named Id.
      
                  if(ep.Name != "Id")
                      _context.Entry(con).Property(ep.Name).IsModified = true;
              }
          }
      
          return _context.SaveChanges();
      }
      
      public static object ToType<T>(object obj, T type)
      {
          // Create an instance of T type object
          object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));
      
          // Loop through the properties of the object you want to convert
          foreach (PropertyInfo pi in obj.GetType().GetProperties())
          {
              try
              {
                  // Get the value of the property and try to assign it to the property of T type object
                  tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
              }
              catch (Exception ex)
              {
                  // Logging.Log.Error(ex);
              }
          }
          // Return the T type object:         
          return tmp;
      }
      

      完整代码如下:

      public interface IContactRepository
      {
          IEnumerable<Contacts> GetAllContats();
          IEnumerable<Contacts> GetAllContactsWithAddress();
          int Update(object c);
      }
      
      public class ContactRepository : IContactRepository
      {
          private ContactContext _context;
      
          public ContactRepository(ContactContext context)
          {
              _context = context;
          }
      
          public IEnumerable<Contacts> GetAllContats()
          {
              return _context.Contacts.OrderBy(c => c.FirstName).ToList();
          }
      
          public IEnumerable<Contacts> GetAllContactsWithAddress()
          {
              return _context.Contacts
                  .Include(c => c.Address)
                  .OrderBy(c => c.FirstName).ToList();
          }   
      
          //TODO Change properties to lambda expression
          public int Update(object entity)
          {
              var entityProperties = entity.GetType().GetProperties();
      
              Contacts con = ToType(entity, typeof(Contacts)) as Contacts;
      
              if (con != null)
              {
                  _context.Entry(con).State = EntityState.Modified;
                  _context.Contacts.Attach(con);
      
                  foreach (var ep in entityProperties)
                  {
                      if(ep.Name != "Id")
                          _context.Entry(con).Property(ep.Name).IsModified = true;
                  }
              }
      
              return _context.SaveChanges();
          }
      
          public static object ToType<T>(object obj, T type)
          {
              // Create an instance of T type object
              object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));
      
              // Loop through the properties of the object you want to convert
              foreach (PropertyInfo pi in obj.GetType().GetProperties())
              {
                  try
                  {
                      // Get the value of the property and try to assign it to the property of T type object
                      tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
                  }
                  catch (Exception ex)
                  {
                      // Logging.Log.Error(ex);
                  }
              }
              // Return the T type object
              return tmp;
          }
      }    
      
      public class Contacts
      {
          public int Id { get; set; }
          public string FirstName { get; set; }
          public string LastName { get; set; }
          public string Email { get; set; }
          public string Company { get; set; }
          public string Title { get; set; }
          public Addresses Address { get; set; }    
      }
      
      public class Addresses
      {
          [Key]
          public int Id { get; set; }
          public string AddressType { get; set; }
          public string StreetAddress { get; set; }
          public string City { get; set; }
          public State State { get; set; }
          public string PostalCode { get; set; }  
      }
      
      public class ContactContext : DbContext
      {
          public DbSet<Addresses> Address { get; set; } 
          public DbSet<Contacts> Contacts { get; set; } 
          public DbSet<State> States { get; set; }
      
          protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
          {
              var connString = "Server=YourServer;Database=ContactsDb;Trusted_Connection=True;MultipleActiveResultSets=true;";
              optionsBuilder.UseSqlServer(connString);
              base.OnConfiguring(optionsBuilder);
          }
      }
      

      【讨论】:

        【解决方案8】:

        我找到了一个很好的方法。

         var Update = context.UpdateTables.Find(id);
                Update.Title = title;
        
                // Mark as Changed
                context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
                context.SaveChanges();
        

        【讨论】:

          【解决方案9】:

          对于 .net 核心

          context.Customer.Add(customer);
          context.Entry(customer).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
          context.SaveChanges();
          

          【讨论】:

          • 这样发送正确的更新还是发送所有属性?假设我有一个 10Mb 文本属性的记录。每次我更新另一个属性时它会发送到数据库吗?
          【解决方案10】:

          这是解决此问题的最佳方法:在视图中添加所有 ID(键)。考虑拥有多个名为(First、Second 和 Third)的表

          @Html.HiddenFor(model=>model.FirstID)
          @Html.HiddenFor(model=>model.SecondID)
          @Html.HiddenFor(model=>model.Second.SecondID)
          @Html.HiddenFor(model=>model.Second.ThirdID)
          @Html.HiddenFor(model=>model.Second.Third.ThirdID)
          

          在 C# 代码中,

          [HttpPost]
          public ActionResult Edit(First first)
          {
            if (ModelState.Isvalid)
            {
              if (first.FirstID > 0)
              {
                datacontext.Entry(first).State = EntityState.Modified;
                datacontext.Entry(first.Second).State = EntityState.Modified;
                datacontext.Entry(first.Second.Third).State = EntityState.Modified;
              }
              else
              {
                datacontext.First.Add(first);
              }
              datacontext.SaveChanges();
              Return RedirectToAction("Index");
            }
          
           return View(first);
          }
          

          【讨论】:

            【解决方案11】:
            using(var myDb = new MyDbEntities())
            {
            
                user user = new user();
                user.username = "me";
                user.email = "me@me.com";
            
                myDb.Users.Add(user);
                myDb.users.Attach(user);
                myDb.Entry(user).State = EntityState.Modified;//this is for modiying/update existing entry
                myDb.SaveChanges();
            }
            

            【讨论】:

              【解决方案12】:

              你应该删除db.Books.Attach(book);

              【讨论】:

                【解决方案13】:

                这是我的 RIA 后实体更新方法(针对 Ef6 时间范围):

                public static void UpdateSegment(ISegment data)
                {
                    if (data == null) throw new ArgumentNullException("The expected Segment data is not here.");
                
                    var context = GetContext();
                
                    var originalData = context.Segments.SingleOrDefault(i => i.SegmentId == data.SegmentId);
                    if (originalData == null) throw new NullReferenceException("The expected original Segment data is not here.");
                
                    FrameworkTypeUtility.SetProperties(data, originalData);
                
                    context.SaveChanges();
                }
                

                请注意,FrameworkTypeUtility.SetProperties() 是我在 NuGet 上的 AutoMapper 之前很久就编写的一个小型实用函数:

                public static void SetProperties<TIn, TOut>(TIn input, TOut output, ICollection<string> includedProperties)
                    where TIn : class
                    where TOut : class
                {
                    if ((input == null) || (output == null)) return;
                    Type inType = input.GetType();
                    Type outType = output.GetType();
                    foreach (PropertyInfo info in inType.GetProperties())
                    {
                        PropertyInfo outfo = ((info != null) && info.CanRead)
                            ? outType.GetProperty(info.Name, info.PropertyType)
                            : null;
                        if (outfo != null && outfo.CanWrite
                            && (outfo.PropertyType.Equals(info.PropertyType)))
                        {
                            if ((includedProperties != null) && includedProperties.Contains(info.Name))
                                outfo.SetValue(output, info.GetValue(input, null), null);
                            else if (includedProperties == null)
                                outfo.SetValue(output, info.GetValue(input, null), null);
                        }
                    }
                }
                

                【讨论】:

                • 注意:仅当您的模型中的属性与保存到其中的 ViewModel 对象完全相同时才有效。
                【解决方案14】:

                就像 Renat 所说,删除:db.Books.Attach(book);

                另外,将您的结果查询更改为使用“AsNoTracking”,因为此查询会抛出实体框架的模型状态。它认为“结果”是现在要跟踪的书,而你不希望这样。

                var result = db.Books.AsNoTracking().SingleOrDefault(b => b.BookNumber == bookNumber);
                

                【讨论】:

                  【解决方案15】:

                  试试看……

                  更新模型(书);

                  var book = new Model.Book
                  {
                      BookNumber =  _book.BookNumber,
                      BookName = _book.BookName,
                      BookTitle = _book.BookTitle,
                  };
                  using (var db = new MyContextDB())
                  {
                      var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
                      if (result != null)
                      {
                          try
                          {
                              UpdateModel(book);
                              db.Books.Attach(book);
                              db.Entry(book).State = EntityState.Modified;
                              db.SaveChanges();
                          }
                          catch (Exception ex)
                          {
                              throw;
                          }
                      }
                  }
                  

                  【讨论】:

                    【解决方案16】:

                    我知道它已经被很好地回答了几次,但我喜欢下面的方式。我希望它会对某人有所帮助。

                    //attach object (search for row)
                    TableName tn = _context.TableNames.Attach(new TableName { PK_COLUMN = YOUR_VALUE});
                    // set new value
                    tn.COLUMN_NAME_TO_UPDATE = NEW_COLUMN_VALUE;
                    // set column as modified
                    _context.Entry<TableName>(tn).Property(tnp => tnp.COLUMN_NAME_TO_UPDATE).IsModified = true;
                    // save change
                    _context.SaveChanges();
                    

                    【讨论】:

                      【解决方案17】:

                      如果适用于实体框架 6.2.0。

                      如果您有特定的DbSet 和需要更新或创建的项目:

                      var name = getNameFromService();
                      
                      var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
                      if (current == null)
                      {
                          _dbContext.Names.Add(name);
                      }
                      else
                      {
                          _dbContext.Entry(current).CurrentValues.SetValues(name);
                      }
                      _dbContext.SaveChanges();
                      

                      但是,这也可以用于具有单个主键或复合主键的通用 DbSet

                      var allNames = NameApiService.GetAllNames();
                      GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");
                      
                      public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class
                      {
                          foreach (var value in values)
                          {
                              try
                              {
                                  var keyList = new List<object>();
                      
                                  //Get key values from T entity based on keyValues property
                                  foreach (var keyValue in keyValues)
                                  {
                                      var propertyInfo = value.GetType().GetProperty(keyValue);
                                      var propertyValue = propertyInfo.GetValue(value);
                                      keyList.Add(propertyValue);
                                  }
                      
                                  GenericAddOrUpdateDbSet(keyList, value);
                                  //Only use this when debugging to catch save exceptions
                                  //_dbContext.SaveChanges();
                              }
                              catch
                              {
                                  throw;
                              }
                          }
                          _dbContext.SaveChanges();
                      }
                      
                      public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class
                      {
                          //Get a DbSet of T type
                          var someDbSet = Set(typeof(T));
                      
                          //Check if any value exists with the key values
                          var current = someDbSet.Find(keyList.ToArray());
                          if (current == null)
                          {
                              someDbSet.Add(value);
                          }
                          else
                          {
                              Entry(current).CurrentValues.SetValues(value);
                          }
                      }
                      

                      【讨论】:

                        【解决方案18】:

                        尝试使用 Attach() 和 SaveChanges() 组合更新记录时,我遇到了同样的问题,但我使用的是 SQLite DB 及其 EF 提供程序(相同的代码在 SQLServer DB 中运行没有问题)。

                        我发现,当您的 DB 列在 SQLite 中具有 GUID(或 UniqueIdentity)并且您的模型是 nvarchar 时,SQLIte EF 默认将其视为二进制(即字节 [])。因此,当 SQLite EF 提供程序尝试将 GUID 转换为模型(在我的情况下为字符串)时,它将失败,因为它将转换为字节 []。修复方法是通过定义“BinaryGUID=false;”告诉 SQLite EF 将 GUID 视为 TEXT(因此转换为字符串,而不是字节 [])在连接字符串(或元数据,如果您首先使用数据库)中,如下所示:

                          <connectionStrings>
                            <add name="Entities" connectionString="metadata=res://savetyping...=System.Data.SQLite.EF6;provider connection string=&quot;data source=C:\...\db.sqlite3;Version=3;BinaryGUID=false;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
                          </connectionStrings>
                        

                        链接到对我有用的解决方案: How does the SQLite Entity Framework 6 provider handle Guids?

                        【讨论】:

                          【解决方案19】:

                          与此特定示例无关,但我在尝试使用 EF 和 DateTime 字段作为并发检查字段时遇到了挑战。似乎 EF 并发代码不遵守元数据 (edmx) 中的精度设置,即 Type="DateTime" Precision="3"。 数据库日期时间字段将在该字段中存储毫秒分量(即 2020-10-18 15:49:02.123)。即使您将 Entity 的原始值设置为包含毫秒组件的 DateTime,SQL EF 生成的也是这样的:

                          UPDATE [dbo].[People]
                          SET [dateUpdated] = @0
                          WHERE (([PeopleID] = @1) AND ([dateUpdated] = @2))
                          -- @0: '10/19/2020 1:07:00 AM' (Type = DateTime2)
                          -- @1: '3182' (Type = Int32)
                          -- @2: '10/19/2020 1:06:10 AM' (Type = DateTime2)
                          

                          如您所见,@2 是一个没有毫秒分量的 STRING 表示。这将导致您的更新失败。

                          因此,如果您要将 DateTime 字段用作并发键,则在检索记录时必须从数据库字段中剥离毫秒/滴答,并且仅使用类似的剥离 DateTime 传递/更新字段。

                              //strip milliseconds due to EF concurrency handling
                              PeopleModel p = db.people.Where(x => x.PeopleID = id);
                              if (p.dateUpdated.Millisecond > 0)
                              {
                                  DateTime d = new DateTime(p.dateUpdated.Ticks / 10000000 * 10000000);
                                  object[] b = {p.PeopleID, d};
                                  int upd = db.Database.ExecuteSqlCommand("Update People set dateUpdated=@p1 where peopleId=@p0", b);
                                  if (upd == 1)
                                      p.dateUpdated = d;
                                  else
                                      return InternalServerError(new Exception("Unable to update dateUpdated"));
                              }
                          return Ok(p);
                          

                          当使用新值更新字段时,还要去掉毫秒

                          (param)int id, PeopleModel person;
                          People tbl = db.People.Where(x => x.PeopleID == id).FirstOrDefault();
                          db.Entry(tbl).OriginalValues["dateUpdated"] = person.dateUpdated;
                          //strip milliseconds from dateUpdated since EF doesn't preserve them
                          tbl.dateUpdated = new DateTime(DateTime.Now.Ticks / 10000000 * 10000000);
                          

                          【讨论】:

                            【解决方案20】:

                            最简单的方法就是这样。

                            var book = new Model.Book
                            {
                                BookNumber =  _book.BookNumber,
                                BookName = _book.BookName,
                                BookTitle = _book.BookTitle,
                            };
                            using (var db = new MyContextDB())
                            {
                                var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
                                if (result != null)
                                {
                                    try
                                    {
                                        // you can't attach book since it doesn't exist in the database yet
                                        // attach result instead
                                        db.Books.Attach(result);
                                        result = book; // this will update all the fields at once
                                        db.SaveChanges();
                                    }
                                    catch (Exception ex)
                                    {
                                        throw;
                                    }
                                }
                            }
                            

                            【讨论】:

                              猜你喜欢
                              • 2018-03-21
                              • 2021-12-14
                              • 1970-01-01
                              • 2013-02-26
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多