我正在尝试使用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:存储 更新、插入或删除语句受影响的数量超出预期 自实体以来,实体可能已被修改或删除 被加载。刷新ObjectStateManager条目


当前回答

Not related to this specific example, but I came across a challenge when trying to use EF and a DateTime field as the concurrency check field. It appears the EF concurrency code doesn't honor the precision setting from the metadata (edmx) i.e. Type="DateTime" Precision="3". The database datetime field will store a millisecond component within the field (i.e. 2020-10-18 15:49:02.123). Even if you set the original value of the Entity to a DateTime that includes the millisecond component, the SQL EF generates is this:

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字段作为并发键,那么在检索记录时必须从数据库字段中剥离毫秒/ tick,并且仅使用类似剥离的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);

其他回答

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

并发性总是很棘手,但我假设您只是希望您的更新能够胜出。下面是我对相同情况的处理方法,并修改名称以模仿您的类。换句话说,只需将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();
    }
}

Not related to this specific example, but I came across a challenge when trying to use EF and a DateTime field as the concurrency check field. It appears the EF concurrency code doesn't honor the precision setting from the metadata (edmx) i.e. Type="DateTime" Precision="3". The database datetime field will store a millisecond component within the field (i.e. 2020-10-18 15:49:02.123). Even if you set the original value of the Entity to a DateTime that includes the millisecond component, the SQL EF generates is this:

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字段作为并发键,那么在检索记录时必须从数据库字段中剥离毫秒/ tick,并且仅使用类似剥离的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);

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

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

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

当尝试使用Attach()和SaveChanges()组合更新记录时,我也有同样的问题,但我使用的是SQLite DB及其EF提供者(相同的代码在SQLServer DB中工作没有问题)。

I found out, when your DB column has GUID (or UniqueIdentity) in SQLite and your model is nvarchar, SQLIte EF treats it as Binary(i.e., byte[]) by default. So when SQLite EF provider tries to convert GUID into the model (string in my case) it will fail as it will convert to byte[]. The fix is to tell the SQLite EF to treat GUID as TEXT (and therefore conversion is into strings, not byte[]) by defining "BinaryGUID=false;" in the connectionstring (or metadata, if you're using database first) like so:

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

链接到对我有效的解决方案: SQLite实体框架6提供者如何处理指南?