关于c#:如何使用Entity Framework 6更新记录?

How to update record using Entity Framework 6?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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: Store
update, insert, or delete statement affected an unexpected number of
rows (0). Entities may have been modified or deleted since entities
were loaded. Refresh ObjectStateManager entrie

  • 旁注:catch (Exception ex){throw;}是多余的,您可以完全删除它。
  • Try-Catch块只是为了找出失败的原因。但还是没明白为什么这个代码会失败?
  • 我不是这个话题的专家,我回答不了这个问题。但如果不使用try catch,也可以使用break-when-exception被抛出功能,在出现异常时中断调试器。
  • 如果你在网站或这个网站上搜索这个错误,你会得到很多提示。你找到了哪些,你试过什么?你的桌子是什么样子的?
  • @codecaster:我的表没有任何非空约束(主键除外,它设置为自动增量)。也签入了SQL事件探查器,如果手动运行相同的查询,它也可以工作。
  • 我的模型类型是数据库优先。
  • 你什么都没变。使用实体状态不会改变对象实际上没有被修改的事实。
  • 嗯,我和你做的一样,没有得到错误。异常表示DBUpdateConcurrencyException。您是如何处理并发的?是否使用了时间戳,是否克隆并再次合并对象,或者是否使用了自我跟踪实体?(3种最常用的方法)。如果您不处理并发性,我想这就是问题所在。
  • 这里的注释/答案中怎么都没有提到book对象是否具有有效的主键值?因为它没有。如果有的话,在发现result后就不能再附着了。这就是DbUpdateConcurrencyException的原因。也许这个答案做了一个半生不熟的尝试来解决这个缺陷,但它并不能真正解释太多。


您试图更新记录(对我来说,这意味着"更改现有记录的值并将其保存")。因此,您需要检索对象,进行更改并保存它。

1
2
3
4
5
6
7
8
9
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        result.SomeValue ="Some new value";
        db.SaveChanges();
    }
}

  • W,这到底是怎么工作的?将值赋给"result"实际上如何更新数据库?
  • 指定该值不会更新数据库,调用db.SaveChanges(),在上下文中使用修改过的对象来更新数据库。
  • 但它仍然让我着迷…所以var result,实际上连接到dbContext…这意味着由任何dbContext成员实例化的任何变量实际上都将与数据库关联,因此无论对该变量应用了什么更改,它都将被应用或持久化?
  • 因为上下文生成了对象,所以上下文可以跟踪对象,包括对对象的更改。当调用SaveChanges时,上下文将评估它跟踪的所有对象,以确定它们是添加、更改还是删除的,并向连接的数据库发出适当的SQL。
  • IAM面临同样的问题-使用EF6,尝试更新实体。附加+EntityState。修改后不工作。唯一有效的方法是-您需要检索对象,进行所需的更改,然后通过db.save changes()保存它;
  • 这个查询相同吗?,var result = db.Books.Where(b => b.BookNumber == bookNumber).SingleOrDefault();FirstOrDefaultSingleOrDefault之间是否有性能考虑?
  • @斯托姆:是的,查询在返回相同结果的意义上是相同的。你必须测试它,看看在你的特定情况下是否有性能差异。
  • 为了更新对象,不应该先检索对象。我遇到了同样的问题,直到我意识到我正试图更改其中一个主键值(复合键)。只要您提供了正确的主键,就可以将EntityState设置为Modified,并且saveChanges()将起作用,前提是您不破坏在表上定义的其他完整性约束。
  • @克雷格,我正在做一个类似的事情,但不是更新,它为我添加了一个新条目。有什么想法吗?stackoverflow.com/questions/51508730/…
  • 结果是通过引用传递的,这就是为什么这是有效的:)
  • 当您使用第3行拉取项目时,它会自动将其设置为更改跟踪。这就是为什么这是有效的。另一种方法是,不需要加载它来跟踪更改(省略第3行),将状态设置为"已修改",然后保存更改。这一切都是基于知道什么时候设置了更改跟踪。


我一直在审查实体框架的源代码,并找到了一种方法来实际更新实体,如果您知道密钥属性,那么在其他情况下,您需要检查addorupdate实现:

1
2
3
4
5
6
7
8
9
10
11
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);
}

希望这有帮助!

  • 好极了!无需枚举所有属性。我假设设置值后需要SaveChanges()调用。
  • 是,更改将保留在savechanges()上
  • 很好的答案,用IntelliSense还不太清楚这样做不会起作用:context.myobj=newobj;然后savechanges()或…._ context.myobj.update(newobj)then savechanges();您的解决方案更新整个对象,而不必遍历所有属性。
  • 这让我抱怨我正试图编辑ID字段
  • 伟大的!!这就是我要找的。
  • _ context.entry(entity).currentValues.setValues(item);ftw!!!!
  • @米盖尔,你需要什么证明人来运行这个?我收到以下错误:t不包含当前上下文中不存在的id_集合的定义
  • t是一个通用参数,请使用您的实体类:user、order、…
  • @Vasilyhall——如果模型之间的ID字段(或定义主键为的任何内容)不同(包括其中一个模型中的空值/0),就会发生这种情况。确保两个模型之间的ID匹配,它会更新得很好。
  • 哇,生活在改变。在设置值以保持主键不变之前,我必须执行以下操作…entity.id=existingentity.id;


您可以使用AddOrUpdate方法:

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

  • IMO最佳解决方案
  • 节省了我几个小时!!!!
  • .AddOrUpdate()在数据库迁移期间使用,强烈建议在迁移之外使用此方法,因此它在Entity.Migrations命名空间中的原因是。
  • 我对这个addorupdate代码做了逆向工程,结果是我的另一个答案。stackoverflow.com/questions/25894587/&hellip;
  • 正如@adamwinnt所说,AddOrUpdate()方法是用于迁移的,它不适合只需要更新现有行的情况。如果没有带有搜索引用(即ID)的书籍,它将创建新行,在某些情况下可能会成为问题(例如,如果尝试为不存在的行调用Put方法,则API需要返回404 NotFound响应)。
  • 最好的解决方案谢谢!
  • 除非你知道你在做什么,否则不要用这个!!!!!!!!!!!!!!!!!!!!!!!阅读:michaelgmcarthy.com/2016/08/24/&hellip;
  • 我今天又回到了这个问题上,我可以警告你们所有人,对于所需的用例来说,这不是一个好的解决方案吗?


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

并发性总是很棘手的,但我假设您只是希望您的更新能够获胜。下面是我如何处理同一个案例并修改名称以模仿您的类。换言之,把attach改为add,对我来说是有用的:

1
2
3
4
5
6
7
8
9
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();
    }
}

此代码是测试的结果,只更新一组列,而不进行首先返回记录的查询。它首先使用实体框架7代码。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 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;
}

以下是完整的代码:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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);
    }
}

您应该使用。entry()方法,用于更新对象中的所有字段。还要记住,您不能更改字段ID(键),因此在编辑时,首先将ID设置为一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}

  • 你至少应该试着回答这个问题,而不仅仅是发布代码。
  • 为了更好地帮助提问者,请对问题做一些解释,而不是只留下一个代码片段。


这里是这个问题的最佳解决方案:在视图中添加所有ID(键)。考虑命名多个表(第一、第二和第三)

1
2
3
4
5
@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代码中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[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);
}

1
2
3
4
5
6
7
8
9
10
11
12
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();
}

  • 非常感谢,这是100%的工作


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


我找到了一个可行的方法。

1
2
3
4
5
6
 var Update = context.UpdateTables.Find(id);
        Update.Title = title;

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

试试吧…

更新模型(书);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
        }
    }
}


如果是实体框架6.2.0。

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

1
2
3
4
5
6
7
8
9
10
11
12
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

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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);
    }
}

以下是我的RIA后实体更新方法(对于EF6时间框架):

1
2
3
4
5
6
7
8
9
10
11
12
13
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之前就写过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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对象完全相同时才有效。


如雷纳所说,删除:db.Books.Attach(book);

此外,将结果查询更改为使用"asnotracking",因为此查询正在丢弃实体框架的模型状态。它认为"结果"就是现在要追踪的书,而你不想这样。

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

对于.NET内核

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

我知道已经回答了好几次了,但我喜欢下面的方法。我希望它能帮助别人。

1
2
3
4
5
6
7
8
//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();