实体和派生视图模型 - 只更新公共属性

问题描述:

说我有一个名为User的类,这是我的基本实体(我使用DbContext作为DbSet Users),我用它作为Data-Access-Layers的底层。比方说,类看起来是这样的:实体和派生视图模型 - 只更新公共属性

public class User 
{ 
    [Key] 
    public int Id { get; set; } 
    public bool Active { get; set; } 
    public string Description { get; set; } 
    public string Username { get; set; } 
    public string Password { get; set; } 
    public byte[] Photo { get; set; } 
    public DateTime Created { get; set; } 
} 

现在我想有纯看法,我只显示用户是否处于活动状态,并且简单的复选框,让我改变的价值。我不想加载任何其他实体属性,特别是属性照片,因为它只是疯了。我创建ActivateUserModel认为是这样的:

public class ActivateUserModel 
{ 
    [Key] 
    public int Id { get; set; } 
    public bool Active { get; set; } 
} 

我都强类型的视图叫做激活,这需要ActivateUserModel并显示它(这只是一个复选框,并隐藏编号),然后我有[HttpPost]激活操作捕获ActivateUserModel,将其转换为User实体,然后将更改保存到数据库。这是POST动作:

[HttpPost] 
    public ActionResult Activate(ActivateUserModel model) 
    { 
     if (ModelState.IsValid) 
     { 
      User user = new User { Id = model.Id, Active = model.Active }; 
      db.Users.Attach(user); 
      db.Entry(user).Property(u => u.Active).IsModified = true; 
      db.SaveChanges(); 
      return RedirectToAction("Index"); 
     } 
     return View(model); 
    } 

这种方式就像一种魅力。我监视了我对SQL服务器发出的查询,并且我加载的所有对都是Id/Active,我更新的所有内容也都是基于id的Active change。

但是我不喜欢它的外观。假设我有拥有50个属性的实体,并且查看了25个。我不想在我说IsModified = true的地方写25行。

所以我的问题是:是否有更有效的方法来做同样的事情,而无需挖掘任何基于反射的方法?我想将数据从任何视图模型转移到实体,然后只保存这些属性。

预先感谢您的答复,我希望我做的问题不够清楚:)

+0

您可以使用Automapper将您的视图模型与现有用户同步。 – 2012-04-29 09:41:11

你可以这样来做:

[HttpPost] 
public ActionResult Activate(ActivateUserModel model) 
{ 
    if (ModelState.IsValid) 
    { 
     User user = db.Users.Single(u => u.Id == model.Id); 
     db.Entry(user).CurrentValues.SetValues(model); 
     db.SaveChanges(); 

     return RedirectToAction("Index"); 
    } 
    return View(model); 
} 

db.Entry(user).CurrentValues.SetValues(model)将检查在user属性也与存在model中的同名,如果是,则将属性值从model复制到user。如果不是,则保留user中的属性值不变。

我怀疑这不是基于反射的。但上面的代码是直接的方式,旨在支持您的场景。

编辑

上面的代码加载完整user实体包括Photo财产。如果你不喜欢加载可能较大的二进制字段,我建议通过除IsModified之外的其他策略来解决此问题。使用实体框架进行更新强烈依赖于需要加载完整实体的更改跟踪。尝试避免此问题时,您将使代码复杂化并手动为特定属性设置Modified标志。

您可能知道,从数据库中获取实体时,您不能排除单个标量属性的加载情况。我建议将Photo资产转换为一个新实体UserPhoto,该实体只有一个IdPhoto属性,并将导航属性UserPhoto放入User类中。然后,如果您要将照片与User一起加载或不加载,则可以通过懒惰,急切或明确的加载来决定。

如果要将UserPhoto存储在单独的表中,您可以在UserUserPhoto之间创建一对一映射。或者,您甚至可以将Photo列保留在User表中,并通过Table Splitting将两个实体UserUserPhoto映射到同一个表。

编辑2

关于你的评论,该方法加载 “不必要的东西”。我忘了提及以下内容:

事实上,在上面的代码中,您有从数据库中加载实体的成本。但是,如果使用将model应用于装载的实体user,那么EF将仅将那些属性标记为Modified,与数据库中的原始值相比,这些属性确实发生了变化。生成的UPDATE语句将只包含这些列。因此,编写UPDATE语句的成本最小化。

如果您不想加载实体,您不知道数据库中的当前列值,并且您不知道确实发生了什么变化。你唯一的机会是强制更新所有属性以确保数据库中的行得到正确更新。在您的检查中,您必须将包含在ViewModel中的所有25个属性的IsModified设置为true。生成的SLQ UPDATE语句将包含全部25列。所以,UPDATE语句可能要贵得多,而且 - 借用你的话 - 不需要的东西。

+0

我很抱歉,但您的建议并没有太大的帮助。你的建议是通常的加载不必要的东西的书本方法。正如我所说的,我的解决方案工作得很好,它可以满足需求,而且对SQL服务器运行的查询也很少(由Anjlab SqlProfiler检查)。我不会牺牲代码缩减的性能。无论如何感谢你的努力。 – 2012-04-29 17:08:39

+0

@BarisaPuter:我在回答中添加了Edit2,我忘了提及它,但也许你知道。顺便说一句:在这个答案(http://stackoverflow.com/a/10271910/270591)是一种方法来设置多个属性修改*如果你知道你想更新的属性名称。但是为了实现这个通用解决方案,您可能需要反射才能从ViewModel中提取属性名称。 – Slauma 2012-04-29 18:11:03

+0

我不明白SQL更新会如何更昂贵?它只包含我需要更新的25列,而不需要实际更新所有50列。 – 2012-04-29 19:14:23