如何使用Entity Framework 6更新记录?


245

我正在尝试使用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条目


7
旁注:catch (Exception ex){throw;}是多余的,可以完全将其删除。
Sriram Sakthivel 2014年

尝试catch块只是为了找出失败的原因。但是仍然不明白为什么这段代码失败了吗?
user1327064 2014年

2
我不是这个主题的专家,我无法回答这个问题。但是如果没有try catch,也可以使用抛出异常时中断功能来在发生异常时中断调试器。
Sriram Sakthivel 2014年

1
你什么都没改变。使用实体状态玩不会改变以下事实:实际上尚未修改对象。
乔纳森·艾伦

1
好吧,我所做的与您相同,没有收到错误。异常显示DbUpdateConcurrencyException。您如何处理并发?您是否使用了时间戳记,是否进行了克隆然后再次合并对象,还是使用了自我跟踪实体?(3种最常用的方法)。如果您不处理并发,那么我想就是问题所在。
El Mac

Answers:


344

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

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

16
分配值不会更新数据库,db.SaveChanges()在上下文中使用已修改的对象进行调用会更新数据库。
Craig W.

6
仍然令我着迷...所以var结果实际上已连接到dbcontext ...因此,这意味着由任何dbcontext成员实例化的任何变量实际上都将具有与数据库的关联,以便对该变量应用任何更改,它还适用还是坚持?
WantIt 2015年

5
由于上下文生成了对象,因此上下文可以跟踪对象,包括对对象的更改。调用时SaveChanges,上下文将评估它正在跟踪的所有对象,以确定它们是否被添加,更改或删除,并向连接的数据库发出适当的SQL。
Craig W.

3
iam面临同样的问题-使用EF6尝试更新实体。Attach + EntityState.Modified无法正常工作。唯一有效的方法是-您需要检索对象,进行所需的更改并通过db.SaveChanges()保存它。
Gurpreet Singh 2015年

7
您不必先检索对象即可更新它。在意识到自己试图更改主键值之一(复合键)之前,我遇到了同样的问题。只要您提供正确的主键,就可以将EntityState设置为Modified,并且只要不破坏表上定义的其他某些完整性约束,SaveChanges()就可以使用。
adrianz

165

我一直在审查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实现以获取想法。

希望对您有所帮助!


12
真好!无需枚举所有属性。我认为SaveChanges()设置值后需要通话。
JanZahradník'16 -10-16

3
是的,更改将保留在SaveChanges()上
Miguel

1
很好的答案,使用IntelliSense尚不清楚这样做是行不通的:_context.MyObj = newObj; 然后是SaveChanges()或.... _context.MyObj.Update(newObj)然后是SaveChanges(); 您的解决方案将更新整个对象,而不必遍历所有属性。
亚当

7
这让我抱怨,我正在尝试编辑ID字段
Vasily

3
@VasilyHall-如果模型之间的ID字段(或您定义的主键定义为任何值)不同(在其中一个模型中为null / 0),则会发生这种情况。确保两个模型之间的ID匹配,并且更新就好。
加文·科茨

51

您可以使用以下AddOrUpdate方法:

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

1
IMO最佳解决方案
Norgul

112
.AddOrUpdate()在数据库迁移期间使用_时,强烈建议在迁移之外使用此方法,因此为什么要在Entity.Migrations命名空间中使用该方法。
亚当·文森特

1
正如@AdamVincent所说,AddOrUpdate()方法是用于迁移的,不适用于仅需要更新现有行的情况。如果您没有带搜索参考(即ID)的书,则会创建新行,并且在某些情况下可能会成为问题(例如,您有一个API,如果您需要返回404-NotFound响应,尝试为不存在的行调用PUT方法)。
马可

4
除非您知道自己在做什么,否则不要使用它!!!!!!阅读:michaelgmccarthy.com/2016/08/24/…–
Yusha

4
我今天再次回到这个问题上,我能警告大家这不是理想用例的好解决方案吗?
Yusha

23

因此,您有一个已更新的实体,并且您想要用最少的代码量在数据库中更新它...

并发总是很棘手,但我假设您只是想赢得更新。这是我针对相同情况所做的,并修改了名称以模仿您的班级。换句话说,只需将更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();
    }
}

10

如果要更新对象中的所有字段,则应使用Entry()方法。另外请记住,您不能更改字段ID(键),因此请首先将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();
}

2
您至少应该尝试回答这个问题,而不仅仅是发布代码
StaceyGirl '17

请对问题做出一些解释,而不是仅仅留下一个代码片段,以帮助更好地向问题提问。
feanor07 '17

9

此代码是仅更新一组列而无需进行查询以首先返回记录的测试结果。它首先使用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);
    }
}

7

对于.net core

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

5

这是针对此问题的最佳解决方案:在“视图”中添加所有ID(键)。考虑将多个表命名为(第一,第二和第三)

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

5

Attach实体将其跟踪状态设置为Unchanged。要更新现有实体,您需要做的就是将跟踪状态设置为Modified。根据EF6文档

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

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

4
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();
}

4

我找到了一种行之有效的方法。

 var Update = context.UpdateTables.Find(id);
        Update.Title = title;

        // Mark as Changed
        context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
        context.SaveChanges();


1

这是我的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()是我在AutoMapper之前在NuGet上编写的一个很小的实用程序函数:

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对象完全相同时,该工程才有效。
vapcguy

1

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

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

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

1

试试吧....

UpdateModel(book);

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

1

我知道已经回答了好几次,但是我喜欢下面的方法。希望对您有所帮助。

//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();

1

对于实体框架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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.