繁体   English   中英

在实体框架中更新时排除属性

[英]Exclude Property on Update in Entity Framework

我一直在寻找一种正确的方法来标记在 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();

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

我们可以这样使用

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

任何正在寻找如何在 EF Core 上实现这一目标的人。 基本相同,但您的 IsModified 需要在添加要更新的模型之后进行。

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

创建具有要更新的有限属性集的新模型。

即,如果您的实体模型是:

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 属性将保持不变。 我使用了简单的模型,但我想你会知道如何使用它。

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

    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
}

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

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

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

    @Html.HiddenFor(m => m.Token)

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

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

我使用 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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM