使用Entity Framework Code First更新记录中的单个属性

17

如何在不先检索记录的情况下更新单个属性?我是在EF Code First 4.1环境中提问的。

假设我有一个映射到数据库Users表的User类:

class User
{
    public int Id {get;set;}
    [Required]
    public string Name {get;set;}
    public DateTime LastActivity {get;set;}
    ...
}
现在我想更新用户的LastActivity。我有用户id。可以通过查询用户记录,将新值设置为LastActivity,然后调用SaveChanges()来轻松实现。但这会导致多余的查询。
我通过使用Attach方法解决了这个问题。但是由于EF如果Name为空会抛出验证异常,所以我将Name设置为随机字符串(不会更新回DB)。但这似乎不是一个优雅的解决方案:
using (var entities = new MyEntities())
{
    User u = new User {Id = id, Name="this wont be updated" };
    entities.Users.Attach(u);
    u.LastActivity = DateTime.Now;
    entities.SaveChanges();
}

如果有人能为我提供更好的解决方案,我将非常感激。由于这是我第一次在SO上提问,请原谅任何错误。

3个回答

36

这是一个验证实现的问题。验证只能验证整个实体,不能像预期的那样仅验证已修改的属性。因此,在您想使用不完整的虚拟对象的情况下,应将验证关闭:

using (var entities = new MyEntities())
{
    entities.Configuration.ValidateOnSaveEnabled = false;

    User u = new User {Id = id, LastActivity = DateTime.Now };
    entities.Users.Attach(u);
    entities.Entry(user).Property(u => u.LastActivity).IsModified = true;
    entities.SaveChanges();
}

如果您希望使用相同的上下文来更新虚拟对象和整个实体,则显然存在问题,因为在进行验证之前会调用 SaveChanges 方法。因此,您无法确定哪些对象需要进行验证,哪些不需要。


我一直在寻找一种禁用保存验证的方法,但是一直没有成功。感谢您提供的解决方案。 - Quang Vo
这个问题看起来在 EF 5 中已经修复了。 - Peter Smith
我想指出,称为并发检查的某些东西可能会导致“0行更新”异常,因为它将在where子句中添加一个检查来匹配rowversion列。(在我的情况下,它是null,因为它是一个存根) - Slight
我们能否使用除了“Id”之外的其他字段来完成相同的操作呢? 例如:User u = new User {Email = "abcd@123.com", LastActivity = DateTime.Now }; 在这里,Email == "abcd@123.com" 可以唯一地标识一个实体。 - dan
这个怎么用来更新导航属性? - MikeW

4

我现在正在处理这个问题。我的解决方法是覆盖DB上下文中的ValidateEntity方法。

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
    var result = base.ValidateEntity(entityEntry, items);

    var errors = new List<DbValidationError>();

    foreach (var error in result.ValidationErrors)
    {
        if (entityEntry.Property(error.PropertyName).IsModified)
        {
            errors.Add(error);
        }
    }

    return new DbEntityValidationResult(entityEntry, errors);
} 

我相信在这方面肯定还有可以改进的地方,不过它似乎比其他选择更好。


1
这不是完整的代码,但我喜欢这个想法。该代码的主要问题是,您将陷入一种情况,在该情况下,您尝试查看已修改的属性不是基元类型或复杂类型,因此会崩溃。您需要针对引用或集合设置条件。 - Patrick Desjardins

3

您可以尝试一种技巧:

context.Database.ExecuteSqlCommand("update [dbo].[Users] set [LastActivity] = @p1 where [Id] = @p2",  
    new System.Data.SqlClient.SqlParameter("p1", DateTime.Now),  
    new System.Data.SqlClient.SqlParameter("p2", id));

这不是黑客行为。不过不幸的是,只能通过整个实体和像这样的原始SQL字符串来直接更新单个列。 - Nicholas Petersen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接