【问题标题】:Exclude Property on Update in Entity Framework在实体框架中更新时排除属性
【发布时间】:2012-09-30 14:13:07
【问题描述】:

我一直在寻找一种正确的方法来标记在 MVC 中更新模型时不会更改的属性。

以这个小模型为例:

class Model
{
    [Key]
    public Guid Id {get; set;}
    public Guid Token {get; set;}

    //... lots of properties here ...
}

那么 MVC 创建的编辑方法如下所示:

[HttpPost]
public ActionResult Edit(Model model)
{
    if (ModelState.IsValid)
    {
        db.Entry(model).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(model);
}

现在,如果我的视图不包含令牌,它将通过该编辑被取消。

我正在寻找这样的东西:

db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).State = PropertyState.Unmodified;
db.SaveChanges();

到目前为止我发现的最好的方法是包容并手动设置我想要包含的所有属性,但我真的只想说要排除哪些。

【问题讨论】:

  • 我不认为这是重复的:我想始终完全排除某个属性的更新。用户应该没有能力改变它。
  • 您可以使用视图模型并仅映射您想要更新的内容。
  • 我可以。有几种方法解决这个问题。但我想知道是否有这样做的好方法,如果有,它是如何工作的。顺便说一句,我对这个 atm 的最小“解决方案”是打开另一笔交易:using (var db2 = new DataContext()) model.Token = db2.Models.Find(model.Id).Token; 但我对这个也不满意。
  • 我承认这是“正确”的做法,但在这种情况下不这样做是有原因的:a)开销,b)不敏捷,c)不可维护/容易出错.所以是的,我拒绝创建两个相同的类,除了一个属性。

标签: c# entity-framework


【解决方案1】:

我们可以这样使用

 db.Entry(model).State = EntityState.Modified;
 db.Entry(model).Property(x => x.Token).IsModified = false;
 db.SaveChanges();

它会更新但没有 Token 属性

【讨论】:

  • 如果您使用AddOrUpdate 会怎样? - 你怎么知道使用EntityState.ModifiedEntityState.Added
  • 更新:要使其在 EF 6 中工作......您需要 db.Model.Attach(model);
  • 请注意这里的顺序很重要:如果在设置db.Entry(model).Property(x => x.Token).IsModified = false; 之后设置db.Entry(model).State = EntityState.Modified;,则属性将在保存时更新。
  • 这也应该在更新模型值之后 db.Entry (model).CurrentValues.SetValues(sourceModel);如果没有,那么属性也会在保存时更新。
【解决方案2】:

任何人都在寻找如何在 EF Core 上实现这一目标。基本上是一样的,但是你的 IsModified 需要在你添加要更新的模型之后。

db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();

【讨论】:

  • 不知道为什么,但我的 EF Core 只有字符串版本,我无法使用 lambda。我改用了 nameof ,但这是要走的路。谢谢
  • 这个答案太“微软化”了,我知道在测试之前它会起作用。与上述评论相反,字符串和 lambda 版本都产生了相同的结果。也许我们正在使用不同的版本。
【解决方案3】:

创建新模型,其中包含您要更新的有限属性集。

即如果您的实体模型是:

public class User
{
    public int Id {get;set;}
    public string Name {get;set;}
    public bool Enabled {get;set;}
}

您可以创建自定义视图模型,允许用户更改名称,但不能更改启用标志:

public class UserProfileModel
{
   public int Id {get;set;}
   public string Name {get;set;}
}

当您想要进行数据库更新时,请执行以下操作:

YourUpdateMethod(UserProfileModel model)
{
    using(YourContext ctx = new YourContext())
    { 
        User user = new User { Id = model.Id } ;   /// stub model, only has Id
        ctx.Users.Attach(user); /// track your stub model
        ctx.Entry(user).CurrentValues.SetValues(model); /// reflection
        ctx.SaveChanges();
    }
}

当您调用此方法时,您将更新 Name,但 Enabled 属性将保持不变。我使用了简单的模型,但我想你会知道如何使用它。

【讨论】:

  • 谢谢,这看起来不错,但这仍然是白名单,而不是属性黑名单。
  • 您将视图模型中没有的内容“列入黑名单”,这不需要额外的编码,您只使用 EF 功能。此外,当使用 Attach 附加存根实体时,所有属性值都设置为 null / 默认值。当您使用 SetValues(model) 时,如果您的视图模型属性为 null,因为它已经附加为 null,更改跟踪器不会将其标记为已修改,因此该属性将被跳过保存。试试看。
  • 我不想和你争论。黑名单和白名单是不同的方法,结果相同,你的方法是白名单。正如我之前所说,有很多方法,但我特别问的是一种。此外,您的解决方案仅适用于可为空的类型。
  • 1.使用 Attach 2 附加您的模型。使用 db.Entry(model).Property("Propertyname").State = PropertyState.Modified; 循环遍历属性3. 执行 SaveChanges。
  • 您所做的是将整个模型设置为要修改,然后将一些属性设置为未修改。我写给你的是先附加模型(没有设置为已修改),然后将要更新的属性标记为已修改,这正是你想要的 => 白名单。
【解决方案4】:

我提供了一种简单的方法来编辑我将与您分享的实体的属性。 此代码将编辑实体的名称和家庭属性:

    public void EditProfileInfo(ProfileInfo profileInfo)
    {
        using (var context = new TestContext())
        {
            context.EditEntity(profileInfo, TypeOfEditEntityProperty.Take, nameof(profileInfo.Name), nameof(profileInfo.Family));
        }
    }

并且此代码将忽略编辑实体的名称和家庭属性,它将编辑其他属性:

    public void EditProfileInfo(ProfileInfo profileInfo)
    {
        using (var context = new TestContext())
        {
            context.EditEntity(profileInfo, TypeOfEditEntityProperty.Ignore, nameof(profileInfo.Name), nameof(profileInfo.Family));
        }
    }

使用这个扩展:

public static void EditEntity<TEntity>(this DbContext context, TEntity entity, TypeOfEditEntityProperty typeOfEditEntityProperty, params string[] properties)
   where TEntity : class
{
    var find = context.Set<TEntity>().Find(entity.GetType().GetProperty("Id").GetValue(entity, null));
    if (find == null)
        throw new Exception("id not found in database");
    if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Ignore)
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            if (properties.Contains(item.Name))
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    else if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Take)
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            if (!properties.Contains(item.Name))
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    else
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    context.SaveChanges();
}

public enum TypeOfEditEntityProperty
{
    Ignore,
    Take
}

【讨论】:

    【解决方案5】:

    我猜您不希望仅在某些情况下更改该属性,因为如果您不想在应用程序中使用它,只需将其从模型中删除即可。

    如果您只想在某些情况下使用它并避免在上述情况下“无效”,您可以尝试:

    • 用HiddenFor隐藏视图中的参数:

      @Html.HiddenFor(m =&gt; m.Token)

    这将使您的原始值保持不变并传递回控制器。

    从您的DBSet 再次将您的对象加载到控制器中并运行此方法。您可以指定应更新或不应更新的参数的白名单和黑名单。

    【讨论】:

    • 您可以在这里找到关于 TryUpdateModel 的精彩讨论:link。正如在经过验证的答案中所说,最好创建视图模型以完全匹配每个视图中所需的属性。
    • 使用 @Html.HiddenFor 会将值写入视图的 HTML 并允许用户在浏览器中使用检查元素并对其进行修改。在他们这样做之后,它仍然被传递给控制器​​,但具有不同的值并将被更新。我刚刚测试过。
    【解决方案6】:

    我使用 dapper,但我的解决方案也适用于 EF。如果您将来可能要更改您的 ORM,我的解决方案可能更适合您。

    class Model
    {
        public Foo { get; set; }
        public Boo { get; set; }
        public Bar { get; set; }
        // More properties...
    
        public void SafeUpdate(Model updateModel, bool updateBoo = false)
        {
            // Notice Foo is excluded
    
            // An optional update
            if (updateBoo)
                Boo = updateModel.Boo;
    
            // A property that is always allowed to be updated
            Bar = updateModel.Bar;
            
            // More property mappings...
        }
    }
    

    如您所见,我只允许更新我希望的属性。

    我的方法的一个缺点是,如果您向模型引入新属性(允许更新),则需要手动更新此方法。但我相信这并不总是不利的,但有时是有利的,因为您需要了解正在更新的内容,这在安全方面可能是有益的。

    让我们看一下这种方法的演示。

    // Some code, DI etc...
    
    public IActionResult Put([FromBody] Model updateModel)
    {
       var safeModel = new Model();
       safeModel.Update(updateModel);
    
       // Add validation logic for safeModel here...
    
       _modelRepository.Update(safeModel);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-29
      • 2013-12-07
      • 1970-01-01
      • 2012-05-19
      相关资源
      最近更新 更多