如何使用实体框架6更新记录?

我正在尝试使用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;
}
}
}

每次我尝试使用上面的代码更新记录时,我都会得到这个错误:

< p > {System.Data.Entity.Infrastructure。DbUpdateConcurrencyException:存储 更新、插入或删除语句受影响的数量超出预期 自实体以来,实体可能已被修改或删除 被加载。刷新ObjectStateManager条目

798390 次浏览

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

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

以下是我的后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);
}
}
}

这段代码是一个测试的结果,该测试只更新了一组列,而没有首先进行查询以返回记录。它首先使用实体框架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);
}
}

我一直在审查实体框架的源代码,并找到了一种方法来实际更新一个实体,如果你知道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的实现。

希望这对你有所帮助!

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

你可以使用AddOrUpdate方法:

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

这里是这个问题的最佳解决方案:在视图中添加所有的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);
}

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

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

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

另外,将结果查询改为使用“AsNoTracking”,因为该查询会抛出实体框架的模型状态。它认为“结果”是现在要追踪的书,而你不想要那个。

var result = db.Books.AsNoTracking().SingleOrDefault(b => b.BookNumber == bookNumber);
如果你想要更新对象中的所有字段,你应该使用Entry()方法。 还要记住,您不能更改字段id(键),因此首先将id设置为与您编辑时相同。< / p >
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();
}

试一试……

调用UpdateModel(书);

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

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

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


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

我知道这个问题已经被回答过几次了,但我喜欢下面的方法。我希望它能帮助到一些人。

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

这是实体框架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);
}
}

对于。net core

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

Attaching实体将其跟踪状态设置为Unchanged。要更新一个现有实体,你所需要做的就是将跟踪状态设置为Modified。根据EF6文档:

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

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

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

我发现,当你的DB列有GUID(或UniqueIdentity)在SQLite和你的模型是nvarchar, SQLite EF把它作为二进制(即。, byte[])。因此,当SQLite EF提供者试图将GUID转换为模型(在我的情况下是字符串)时,它将失败,因为它将转换为字节[]。修复是告诉SQLite EF处理GUID作为文本(因此转换为字符串,而不是字节[])通过定义"BinaryGUID=false;"在connectionstring(或元数据,如果你先使用数据库)中,如下所示:

  <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提供者如何处理指南? < / p >

与这个特定的例子无关,但我在尝试使用EF和DateTime字段作为并发检查字段时遇到了一个挑战。EF并发代码似乎不尊重元数据(edmx)的精度设置,即Type="DateTime"精度=“3“。 数据库datetime字段将在该字段中存储一个毫秒组件(例如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字段作为并发键,那么在检索记录时必须从数据库字段中剥离毫秒/ 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);

最简单的方法是这样的。

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