Исключить свойство при обновлении в Entity Framework

79

Я искал правильный способ отметить свойство, которое НЕ должно изменяться при обновлении модели в 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();

На данный момент лучший способ, который я нашел, - это включить все свойства и вручную задать все свойства, которые я хочу включить, но на самом деле я хочу только сказать, какие из них следует исключить.

Мануэль Швайгерт
источник
Возможный дубликат: stackoverflow.com/questions/3809583/…
Nate-Wilkins,
Я не думаю, что это дублирование: я хочу всегда исключать какое-то свойство из обновления. У пользователя не должно быть возможности изменить его.
Мануэль Швайгерт
2
вы можете использовать модели просмотра и просто отображать то, что вы хотите обновить.
frennky
Я мог бы. Есть несколько способов обойти эту проблему. Но я хочу знать, есть ли хороший способ сделать это, и если он есть, то как он работает. Кстати, наименьшее «решение», которое у меня есть для этого банкомата, - это открыть другую транзакцию: using (var db2 = new DataContext()) model.Token = db2.Models.Find(model.Id).Token;но и эта меня не устраивает.
Мануэль Швайгерт
3
Я признаю, что это «правильный» способ сделать это, но есть причины, по которым не следует делать это в данном случае: а) накладные расходы, б) не гибкость, в) неподдерживаемость / подверженность ошибкам. Так что да, я отказываюсь создавать два одинаковых класса, кроме одного свойства.
Мануэль Швайгерт

Ответы:

156

мы можем использовать вот так

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

он будет обновляться, но без свойства Token

Нитин Доминик
источник
2
Что делать, если вы используете AddOrUpdate? - Откуда вы знаете, что использовать EntityState.Modifiedили EntityState.Added?
Джесс
1
ОБНОВЛЕНИЕ: чтобы он работал в EF 6 .. вам нужно db.Model.Attach (model);
Макси
6
Просто обратите внимание на то, что порядок здесь важен: если вы установите его db.Entry(model).State = EntityState.Modified;после настройки db.Entry(model).Property(x => x.Token).IsModified = false; , свойство будет обновлено при сохранении.
Акерра
1
А также это должно быть после обновления значений модели db.Entry (model) .CurrentValues.SetValues ​​(sourceModel); Если нет, то свойство также обновляется при сохранении.
DotNet Fan
10

Создайте новую модель, которая будет иметь ограниченный набор свойств, которые вы хотите обновить.

Т.е. если ваша модель сущности:

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

Вы можете создать собственную модель представления, которая позволит пользователю изменять имя, но не флаг Enabled:

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();
    }
}

При вызове этого метода вы обновите имя, но свойство Enabled останется без изменений. Я использовал простые модели, но думаю, вы поймете, как их использовать.

Адмир Тузович
источник
спасибо, это выглядит хорошо, но это все еще белый список, а не черный список свойств.
Мануэль Швайгерт
Вы заносите в черный список все, чего нет в вашей модели представления, и это не требует дополнительного кодирования, вы используете только функции EF. Кроме того, когда объект-заглушка присоединяется с помощью Attach, все значения свойств устанавливаются на null / default. Когда вы используете SetValues ​​(модель), если ваше свойство модели представления имеет значение null, поскольку оно уже было прикреплено как null, средство отслеживания изменений не будет отмечать его как измененное, и, таким образом, это свойство не будет сохранено. Попытайся.
Адмир Тузович
3
Я не хочу с тобой спорить. Внесение в черный и белый списки - это разные подходы с одним и тем же результатом, ваш подход - белый список. Как я сказал ранее, есть много способов, но я спрашивал в частности об одном. Кроме того, ваше решение работает только с типами, допускающими значение NULL.
Мануэль Швайгерт
1. Присоедините вашу модель с помощью Attach 2. Прокрутите свойства с помощью db.Entry (model) .Property ("Propertyname"). State = PropertyState.Modified; 3. Сделайте SaveChanges.
Адмир Тузович
Что вы делаете, так это настраиваете всю модель на изменение, а затем устанавливаете для некоторых свойств значение Не изменено. Я написал вам сначала прикрепите модель (ничего не установлено как измененное), а затем отметьте свойства, которые вы хотите обновить, как измененные, что именно то, что вы хотели => занесение в белый список.
Адмир Тузович 01
8

Всем, кто ищет, как этого добиться в EF Core. Это в основном то же самое, но ваш IsModified должен быть после добавления модели для обновления.

db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
Джесси
источник
Не знаю почему, но у моего EF Core была только строковая версия, и я не мог использовать лямбда. Вместо этого я использовал nameof, но это правильный путь. Спасибо
Cubelaster
Этот ответ настолько "Microsofty", что я знал, что он будет работать, прежде чем тестировать его. Вопреки приведенному выше комментарию, строковая и лямбда-версия дали одинаковые результаты. возможно, мы используем разные версии.
T3.0 01
3

Я сделал простой способ редактировать свойства объектов, которыми поделюсь с вами. этот код будет редактировать свойства Name и Family объекта:

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

И этот код будет игнорировать редактирование свойств Name и 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
}
Али Юсефи
источник
1

Я полагаю, вы не хотите, чтобы свойство изменялось только в некоторых случаях, потому что, если вы не собираетесь использовать его никогда в своем приложении, просто удалите его из своей модели.

Если вы хотите использовать его только в некоторых сценариях и избежать его «обнуления» в приведенном выше случае, вы можете попробовать:

  • Скрыть параметр в представлении с помощью HiddenFor:

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

В результате исходное значение останется неизменным и будет передано контроллеру.

Снова загрузите ваш объект в контроллер из вашего DBSetи запустите этот метод. Вы можете указать как белый список, так и черный список параметров, которые должны или не должны обновляться.

Хайме
источник
Вы можете найти хорошее обсуждение TryUpdateModel здесь: ссылка . Как сказано в подтвержденном ответе, лучше создавать модели просмотра, которые точно соответствуют свойствам, необходимым в каждом представлении.
Хайме
1
Использование @Html.HiddenForзапишет значение в HTML-код представления и позволит пользователю использовать элемент inspect в своем браузере и изменять его. После этого он по-прежнему передается контроллеру, но с другим значением и будет обновлен. Я только что это проверил.
duckwizzle