asp.net-mvc 使用实体框架仅更新修改过的字段的最佳方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33137514/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best Way to Update only modified fields with Entity Framework
提问by 10K35H 5H4KY4
Currently I am doing like this:
目前我这样做:
For Example:
例如:
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
db.Entry(oldobj).CurrentValues.SetValues(model);
}
It works, but for example,
它有效,但例如,
I have 50 columns in my table but I displayed only 25 fields in my form (I need to partially update my table, with remaining 25 column retain same old value)
我的表中有 50 列,但我的表单中只显示了 25 个字段(我需要部分更新我的表,剩余的 25 列保留相同的旧值)
I know it can be achieve by "mapping columns one by one" or by creating "hidden fields for those remaining 25 columns".
我知道这可以通过“一一映射列”或通过创建“剩余 25 列的隐藏字段”来实现。
Just wondering is there any elegant way to do this with less effort and optimal performance?
只是想知道有没有什么优雅的方法可以以更少的努力和最佳性能来做到这一点?
采纳答案by Laurence Frost
This is a very good question. By default I have found that as long as change tracking is enabled (it is by default unless you turn it off), Entity Framework will do a good job of applying to the database only what you ask it to change.
这个问题问得好。默认情况下,我发现只要启用更改跟踪(默认情况下,除非您将其关闭),实体框架将很好地仅将您要求更改的内容应用于数据库。
So if you only change 1 field against the object and then call SaveChanges(), EF will only update that 1 field when you call SaveChanges().
因此,如果您仅针对对象更改 1 个字段,然后调用 SaveChanges(),则 EF 只会在您调用 SaveChanges() 时更新该 1 个字段。
The problem here is that when you map a view model into an entity object, all of the values get overwritten. Here is my way of handling this:
这里的问题是,当您将视图模型映射到实体对象时,所有值都会被覆盖。这是我的处理方式:
In this example, you have a single entity called Person:
在此示例中,您有一个名为 Person 的实体:
Person
======
Id - int
FirstName - varchar
Surname - varchar
Dob - smalldatetime
Now let's say we want to create a view model which will only update Dob, and leave all other fields exactly how they are, here is how I do that.
现在假设我们想要创建一个只会更新 Dob 的视图模型,而让所有其他字段保持原样,这就是我如何做到的。
First, create a view model:
首先,创建一个视图模型:
public class PersonDobVm
{
public int Id { get; set; }
public DateTime Dob { get; set; }
public void MapToModel(Person p)
{
p.Dob = Dob;
}
}
Now write the code roughly as follows (you'll have to alter it to match your context name etc):
现在大致如下编写代码(您必须更改它以匹配您的上下文名称等):
DataContext db = new DataContext();
Person p = db.People.FirstOrDefault();
// you would have this posted in, but we are creating it here just for illustration
var vm = new PersonDobVm
{
Id = p.Id, // the Id you want to update
Dob = new DateTime(2015, 1, 1) // the new DOB for that row
};
vm.MapToModel(p);
db.SaveChanges();
The MapToModel method could be even more complicated and do all kinds of additional checks before assigning the view model fields to the entity object.
MapToModel 方法可能更复杂,并在将视图模型字段分配给实体对象之前执行各种额外的检查。
Anyway, the result when SaveChanges is called is the following SQL:
无论如何,调用 SaveChanges 时的结果是以下 SQL:
exec sp_executesql N'UPDATE [dbo].[Person]
SET [Dob] = @0
WHERE ([Id] = @1)
',N'@0 datetime2(7),@1 int',@0='2015-01-01 00:00:00',@1=1
So you can clearly see, Entity Framework has not attempted to update any other fields - just the Dob field.
所以你可以清楚地看到,Entity Framework 没有尝试更新任何其他字段——只是 Dob 字段。
I know in your example you want to avoid coding each assignment by hand, but I think this is the best way. You tuck it all away in your VM so it does not litter your main code, and this way you can cater for specific needs (i.e. composite types in there, data validation, etc). The other option is to use an AutoMapper, but I do not think they are safe. If you use an AutoMapper and spelt "Dob" as "Doob" in your VM, it would not map "Doob" to "Dob", nor would it tell you about it! It would fail silently, the user would think everything was ok, but the change would not be saved.
我知道在你的例子中你想避免手工编码每个作业,但我认为这是最好的方法。你把它全部放在你的虚拟机中,这样它就不会乱扔你的主代码,这样你就可以满足特定的需求(即那里的复合类型、数据验证等)。另一种选择是使用 AutoMapper,但我认为它们不安全。如果您使用 AutoMapper 并在您的 VM 中将“Dob”拼写为“Doob”,它不会将“Doob”映射到“Dob”,也不会告诉您有关它的信息!它会默默地失败,用户会认为一切正常,但不会保存更改。
Whereas if you spelt "Dob" as "Doob" in your VM, the compiler will alert you that the MapToModel() is referencing "Dob" but you only have a property in your VM called "Doob".
而如果您在 VM 中将“Dob”拼写为“Doob”,编译器会提醒您 MapToModel() 正在引用“Dob”,但您的 VM 中只有一个名为“Doob”的属性。
I hope this helps you.
我希望这可以帮助你。
回答by Andy V
I swear by EntityFramework.Extended. Nuget Link
我发誓 EntityFramework.Extended。 纽吉特链接
It lets you write:
它让你写:
db.Person
.Where(x => x.ID == model.ID)
.Update(p => new Person()
{
Name = newName,
EditCount = p.EditCount+1
});
Which is very clearly translated into SQL.
这很清楚地翻译成SQL。
回答by 10K35H 5H4KY4
I have solved my Issue by using FormCollection to list out used element in form, and only change those columns in database.
我已经通过使用 FormCollection 列出表单中使用的元素解决了我的问题,并且只更改了数据库中的那些列。
I have provided my code sample below; Great if it can help someone else
我在下面提供了我的代码示例;如果它可以帮助别人,那就太好了
// Here
// collection = FormCollection from Post
// model = View Model for Person
var result = db.Person.Where(x => x.ID == model.ID).SingleOrDefault();
if (result != null)
{
List<string> formcollist = new List<string>();
foreach (var key in collection.ToArray<string>())
{
// Here apply your filter code to remove system properties if any
formcollist.Add(key);
}
foreach (var prop in result.GetType().GetProperties())
{
if( formcollist.Contains(prop.Name))
{
prop.SetValue(result, model.GetType().GetProperty(prop.Name).GetValue(model, null));
}
}
db.SaveChanges();
}
回答by Chris Rosete
This is way I did it, assuming the new object has more columns to update that the one we want to keep.
我就是这样做的,假设新对象有更多的列来更新我们想要保留的列。
if (theClass.ClassId == 0)
{
theClass.CreatedOn = DateTime.Now;
context.theClasses.Add(theClass);
}
else {
var currentClass = context.theClasses.Where(c => c.ClassId == theClass.ClassId)
.Select(c => new TheClasses {
CreatedOn = c.CreatedOn
// Add here others fields you want to keep as the original record
}).FirstOrDefault();
theClass.CreatedOn = currentClass.CreatedOn;
// The new class will replace the current, all fields
context.theClasses.Add(theClass);
context.Entry(theClass).State = EntityState.Modified;
}
context.SaveChanges();
回答by Arkar Moe
Please try this way
请尝试这种方式
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
// Newly Inserted Code
var UpdatedObj = (Person) Entity.CheckUpdateObject(oldobj, model);
db.Entry(oldobj).CurrentValues.SetValues(UpdatedObj);
}
public static object CheckUpdateObject(object originalObj, object updateObj)
{
foreach (var property in updateObj.GetType().GetProperties())
{
if (property.GetValue(updateObj, null) == null)
{
property.SetValue(updateObj,originalObj.GetType().GetProperty(property.Name)
.GetValue(originalObj, null));
}
}
return updateObj;
}

