Как обновить только одно поле с помощью Entity Framework?

190

Вот таблица

пользователей

UserId
UserName
Password
EmailAddress

и код ..

public void ChangePassword(int userId, string password){
//code to update the password..
}
H3N
источник
26
К Password, вы имеете в виду хэш пароля, не так ли? :-)
Эдвард Брей

Ответы:

370

Ответ Ладислава обновлен для использования DbContext (представлен в EF 4.1):

public void ChangePassword(int userId, string password)
{
  var user = new User() { Id = userId, Password = password };
  using (var db = new MyEfContextName())
  {
    db.Users.Attach(user);
    db.Entry(user).Property(x => x.Password).IsModified = true;
    db.SaveChanges();
  }
}
Стюарт
источник
55
Я смог заставить этот код работать, только добавив db.Configuration.ValidateOnSaveEnabled = false; до db.SaveChanges ()?
Джейк Дрю
3
Какое пространство имен включить db.Entry(user).Property(x => x.Password).IsModified = true;и нетdb.Entry(user).Property("Password").IsModified = true;
Johan
5
Этот подход вызывает исключение OptimisticConcurencyException, когда в таблице есть поле метки времени.
Максим Ви.
9
Я думаю, стоит упомянуть, что, если вы используете, db.Configuration.ValidateOnSaveEnabled = false;вы можете продолжить проверять поле, которое вы обновляете:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul
2
Вам необходимо установить для ValidateOnSaveEnabled значение false, если у вас есть обязательные поля в таблице, которые вы не предоставляете во время обновления
Sal
54

Вы можете сказать EF, какие свойства должны быть обновлены таким образом:

public void ChangePassword(int userId, string password)
{
  var user = new User { Id = userId, Password = password };
  using (var context = new ObjectContext(ConnectionString))
  {
    var users = context.CreateObjectSet<User>();
    users.Attach(user);
    context.ObjectStateManager.GetObjectStateEntry(user)
      .SetModifiedProperty("Password");
    context.SaveChanges();
  }
}
Ладислав Мрнка
источник
ObjectStateManager не доступен для DBContext
LoxLox
17

У вас есть в основном два варианта:

  • пройти весь путь до конца, в этом случае вы бы
    • загрузить объект на основе userIdпредоставленного - весь объект загружается
    • обновить passwordполе
    • сохранить объект обратно с помощью контекста в .SaveChanges()метод

В этом случае это зависит от EF, как обращаться с этим подробно. Я только что проверил это, и в случае, если я изменяю только одно поле объекта, то, что создает EF, - это почти то же самое, что вы бы тоже создали вручную - что-то вроде:

`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`

Таким образом, EF достаточно умен, чтобы выяснить, какие столбцы действительно изменились, и он создаст оператор T-SQL для обработки только тех обновлений, которые действительно необходимы.

  • Вы определяете хранимую процедуру, которая делает именно то, что вам нужно, в коде T-SQL (просто обновите Passwordстолбец для заданного UserIdи ничего больше - в основном выполняется UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId), и вы создаете функцию импорта для этой хранимой процедуры в вашей модели EF, и вы вызываете это функция вместо того, чтобы делать шаги, описанные выше
marc_s
источник
1
@ marc-s На самом деле вам не нужно загружать весь объект!
Арванд
14

В Entity Framework Core Attachвозвращает запись, поэтому все, что вам нужно, это:

var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
Эдвард Брей
источник
12

Я использую это:

организация:

public class Thing 
{
    [Key]
    public int Id { get; set; }
    public string Info { get; set; }
    public string OtherStuff { get; set; }
}

DbContext:

public class MyDataContext : DbContext
{
    public DbSet<Thing > Things { get; set; }
}

код доступа:

MyDataContext ctx = new MyDataContext();

// FIRST create a blank object
Thing thing = ctx.Things.Create();

// SECOND set the ID
thing.Id = id;

// THIRD attach the thing (id is not marked as modified)
db.Things.Attach(thing); 

// FOURTH set the fields you want updated.
thing.OtherStuff = "only want this field updated.";

// FIFTH save that thing
db.SaveChanges();
groggyjava
источник
1
Когда я пытаюсь это сделать, я получаю ошибки проверки сущности, но это выглядит здорово.
Devlord
Не работает этот метод !!!: может быть, вам нужно дать более подробную информацию, как его использовать !!! - это ошибка: «Не удалось подключить объект типа« Domain.Job », потому что другой объект того же типа уже имеет такое же значение первичного ключа. Это может произойти при использовании метода« Присоединить »или установке состояния объекта в значение «Не изменено» или «Изменено», если какие-либо объекты в графе имеют конфликтующие значения ключей. Это может быть связано с тем, что некоторые объекты являются новыми и еще не получили значения ключей, сгенерированные базой данных ».
Лучиан Бамб
Perfec! Проверьте мой ответ, чтобы увидеть гибкий подход для любой модели!
крип
10

В поисках решения этой проблемы я нашел вариант ответа GONeale в блоге Патрика Дежарденса :

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
  DatabaseContext.Entry(entity).State = EntityState.Unchanged;
  foreach (var property in properties)
  {
    var propertyName = ExpressionHelper.GetExpressionText(property);
    DatabaseContext.Entry(entity).Property(propertyName).IsModified = true;
  }
  return DatabaseContext.SaveChangesWithoutValidation();
}

« Как видите, он принимает в качестве второго параметра выражение функции. Это позволит использовать этот метод, указав в лямбда-выражении, какое свойство обновлять ».

...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);

(Несколько похожее решение также приводится здесь: https://stackoverflow.com/a/5749469/2115384 )

Метод, который я сейчас использую в своем собственном коде , расширен также для обработки (Linq) выражений типа ExpressionType.Convert. Это было необходимо в моем случае, например, с Guidдругими свойствами объекта. Они были «обернуты» в Convert () и поэтому не были обработаны System.Web.Mvc.ExpressionHelper.GetExpressionText.

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
    DbEntityEntry<T> entry = dataContext.Entry(entity);
    entry.State = EntityState.Unchanged;
    foreach (var property in properties)
    {
        string propertyName = "";
        Expression bodyExpression = property.Body;
        if (bodyExpression.NodeType == ExpressionType.Convert && bodyExpression is UnaryExpression)
        {
            Expression operand = ((UnaryExpression)property.Body).Operand;
            propertyName = ((MemberExpression)operand).Member.Name;
        }
        else
        {
            propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
        }
        entry.Property(propertyName).IsModified = true;
    }

    dataContext.Configuration.ValidateOnSaveEnabled = false;
    return dataContext.SaveChanges();
}
Доку-так
источник
1
Когда я использую это, я получаю следующую ошибку: «Невозможно преобразовать лямбда-выражение в тип« Выражение <Func <RequestDetail, объект >> [] », поскольку это не тип делегата
Имран Ризви,
@ImranRizvi, вам просто нужно обновить параметры: public int Update (свойства T, параметры params <Func <T, object >> [] свойства) ПРИМЕЧАНИЕ ключевое слово params перед выражением
dalcam
6

Я опаздываю к игре здесь, но вот как я это делаю, я потратил некоторое время на поиски решения, которое меня удовлетворило; это создает UPDATEоператор ТОЛЬКО для измененных полей, так как вы явно определяете их через концепцию «белого списка», которая в любом случае более безопасна для предотвращения внедрения веб-форм.

Выдержка из моего хранилища данных ISession:

public bool Update<T>(T item, params string[] changedPropertyNames) where T 
  : class, new()
{
    _context.Set<T>().Attach(item);
    foreach (var propertyName in changedPropertyNames)
    {
        // If we can't find the property, this line wil throw an exception, 
        //which is good as we want to know about it
        _context.Entry(item).Property(propertyName).IsModified = true;
    }
    return true;
}

Это можно обернуть в попытку… поймать, если вы того пожелаете, но мне лично нравится, чтобы звонивший знал об исключениях в этом сценарии.

Он будет вызываться примерно так (для меня это было через веб-API ASP.NET):

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
GONeale
источник
2
Значит, твое лучшее решение - это то, что Элиза? Вы должны явно указать, какие свойства вы разрешаете обновлять (точно так же, как белый список, необходимый для команды ASP.NET MVC UpdateModel), таким образом вы гарантируете, что внедрение хакерской формы не может произойти, и они не могут обновить поля, которые им не разрешено обновлять. Если, однако, кто-то может преобразовать строковый массив в какой-то параметр лямбда-выражений и работать с ним в Update<T>отличном состоянии
GONeale
3
@ GONeale - просто проходит. Кто-то взломал его, используя Lambdas!
Дэвид Спенс
1
@Elisa Это можно улучшить, используя Func <T, List <object >> вместо строки []
Губка Боб Товарищ
Даже позже в игре, и, возможно, это гораздо более поздний синтаксис, но var entity=_context.Set<T>().Attach(item);затем entity.Property(propertyName).IsModified = true;в цикле должно работать.
Auspex
4

Entity Framework отслеживает ваши изменения объектов, которые вы запросили из базы данных через DbContext. Например, если у вас имя экземпляра DbContext - dbContext

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}
Бахтияр Оздере
источник
И как должен выглядеть вид в этом случае?
Эмануэла Кольта
Это неправильно, потому что это сохранит весь объект User с измененным паролем.
amuliar
это правда, но остальная часть объекта User будет такой же, как это было ранее в контексте, единственное, что, возможно, будет отличаться, это пароль, так что, по сути, он только обновляет пароль.
Tomislav3008
3

Я знаю, что это старая ветка, но я также искал похожее решение и решил воспользоваться решением @ Doku-so. Я комментирую, чтобы ответить на вопрос, заданный @Imran Rizvi, я перешел по ссылке @ Doku-so, на которой показана аналогичная реализация. Вопрос Имрана Ризви состоял в том, что он получал ошибку, используя предоставленное решение «Невозможно преобразовать лямбда-выражение в тип« Выражение> [] », потому что это не тип делегата». Я хотел предложить небольшую модификацию решения @ Doku-so, которую я сделал, чтобы исправить эту ошибку в случае, если кто-то еще наткнется на этот пост и решит использовать решение @ Doku-so.

Проблема является вторым аргументом в методе обновления,

public int Update(T entity, Expression<Func<T, object>>[] properties). 

Для вызова этого метода используется синтаксис, предоставленный ...

Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn); 

Вы должны добавить ключевое слово 'params' перед вторым arugment как таковое.

public int Update(T entity, params Expression<Func<T, object>>[] properties)

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

Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });

В примере @ Doku-so он указывает массив выражений, поэтому вы должны передать свойства для обновления в массиве, потому что для массива вы также должны указать размер массива. Чтобы избежать этого, вы также можете изменить аргумент выражения, чтобы использовать IEnumerable вместо массива.

Вот моя реализация решения @ Doku-so.

public int Update<TEntity>(LcmsEntities dataContext, DbEntityEntry<TEntity> entityEntry, params Expression<Func<TEntity, object>>[] properties)
     where TEntity: class
    {
        entityEntry.State = System.Data.Entity.EntityState.Unchanged;

        properties.ToList()
            .ForEach((property) =>
            {
                var propertyName = string.Empty;
                var bodyExpression = property.Body;
                if (bodyExpression.NodeType == ExpressionType.Convert
                    && bodyExpression is UnaryExpression)
                {
                    Expression operand = ((UnaryExpression)property.Body).Operand;
                    propertyName = ((MemberExpression)operand).Member.Name;
                }
                else
                {
                    propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
                }

                entityEntry.Property(propertyName).IsModified = true;
            });

        dataContext.Configuration.ValidateOnSaveEnabled = false;

        return dataContext.SaveChanges();
    }

Использование:

this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);

@ Doku-so предоставил классный подход с использованием дженериков, я использовал концепцию для решения своей проблемы, но вы просто не можете использовать решение @ Doku-so как есть, и в этом посте, и в связанном посте никто не ответил на вопросы об ошибках использования.

PWS
источник
Я работал над вашим решением, когда программа передает строку, entityEntry.State = EntityState.Unchanged;все обновленные значения в параметре entityEntryget возвращаются, поэтому никакие изменения не сохраняются, не могли бы вы помочь с этим, спасибо
sairfan
3

В EntityFramework Core 2.x нет необходимости Attach:

 // get a tracked entity
 var entity = context.User.Find(userId);
 entity.someProp = someValue;
 // other property changes might come here
 context.SaveChanges();

Попробовал это в SQL Server и профилировать его:

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [User] SET [someProp] = @p0
WHERE [UserId] = @p1;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 bit',@p1=1223424,@p0=1

Find гарантирует, что уже загруженные сущности не вызывают SELECT, а также автоматически присоединяет сущность при необходимости (из документов):

    ///     Finds an entity with the given primary key values. If an entity with the given primary key values
    ///     is being tracked by the context, then it is returned immediately without making a request to the
    ///     database. Otherwise, a query is made to the database for an entity with the given primary key values
    ///     and this entity, if found, is attached to the context and returned. If no entity is found, then
    ///     null is returned.
Алексей
источник
1

Объединяя несколько предложений, я предлагаю следующее:

    async Task<bool> UpdateDbEntryAsync<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class
    {
        try
        {
            var entry = db.Entry(entity);
            db.Set<T>().Attach(entity);
            foreach (var property in properties)
                entry.Property(property).IsModified = true;
            await db.SaveChangesAsync();
            return true;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("UpdateDbEntryAsync exception: " + ex.Message);
            return false;
        } 
    }

называется

UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);

Или

await UpdateDbEntryAsync(dbc, d => d.Property1);

Или

bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;
парень
источник
Как сделать этот метод доступным для другого класса, может быть как метод расширения?
Велкумар
В этом уроке .NET CORE они показывают лучшие практики использования (нового) EF Core для обновления определенных свойств в MVC. ищите 'TryUpdateModelAsync'.
Парень
1
@ Гай Круто. Тем не менее, еще раз «лучшая практика» Microsoft - это делать что-то помимо того, что создают их инструменты ...
Auspex
Это хорошее решение.
Тимоти Мачария
1

Я использую ValueInjecternuget для внедрения Binding Model в Entity базы данных, используя следующее:

public async Task<IHttpActionResult> Add(CustomBindingModel model)
{
   var entity= await db.MyEntities.FindAsync(model.Id);
   if (entity== null) return NotFound();

   entity.InjectFrom<NoNullsInjection>(model);

   await db.SaveChangesAsync();
   return Ok();
}

Обратите внимание на использование пользовательского соглашения, которое не обновляет свойства, если они нулевые с сервера.

ValueInjecter v3 +

public class NoNullsInjection : LoopInjection
{
    protected override void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
    {
        if (sp.GetValue(source) == null) return;
        base.SetValue(source, target, sp, tp);
    }
}

Использование:

target.InjectFrom<NoNullsInjection>(source);

Значение Injecter V2

Найти этот ответ

Предостережение

Вы не будете знать, было ли свойство намеренно очищено до нуля, ИЛИ оно просто не имело никакого значения. Другими словами, значение свойства может быть заменено только другим значением, но не очищено.

Korayem
источник
0

Я искал то же самое и, наконец, я нашел решение

using (CString conn = new CString())
{
    USER user = conn.USERs.Find(CMN.CurrentUser.ID);
    user.PASSWORD = txtPass.Text;
    conn.SaveChanges();
}

поверьте мне, это работает для меня как шарм.

Бурхан Ул Хакк Захир
источник
0

Это то, что я использую, используя собственный InjectNonNull (obj dest, obj src), что делает его полностью гибким

[HttpPost]
public async Task<IActionResult> Post( [FromQuery]Models.Currency currency ) {
  if ( ModelState.IsValid ) {
    // find existing object by Key
    Models.Currency currencyDest = context.Currencies.Find( currency.Id ); 

    context.Currencies.Attach( currencyDest );

    // update only not null fields
    InjectNonNull( currencyDest, currency );

    // save
    await context.SaveChangesAsync( );
  }  
  return Ok();
}

// Custom method
public static T InjectNonNull<T>( T dest, T src ) {
  foreach ( var propertyPair in PropertyLister<T, T>.PropertyMap ) {
    var fromValue = propertyPair.Item2.GetValue( src, null );
    if ( fromValue != null && propertyPair.Item1.CanWrite ) {
       propertyPair.Item1.SetValue( dest, fromValue, null );
    }
  }
  return dest;
}
kryp
источник
-1
public async Task<bool> UpdateDbEntryAsync(TEntity entity, params Expression<Func<TEntity, object>>[] properties)
{
    try
    {
        this.Context.Set<TEntity>().Attach(entity);
        EntityEntry<TEntity> entry = this.Context.Entry(entity);
        entry.State = EntityState.Modified;
        foreach (var property in properties)
            entry.Property(property).IsModified = true;
        await this.Context.SaveChangesAsync();
        return true;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}
пинту гупта
источник
-7
public void ChangePassword(int userId, string password)
{
  var user = new User{ Id = userId, Password = password };
  using (var db = new DbContextName())
  {
    db.Entry(user).State = EntityState.Added;
    db.SaveChanges();
  }
}
Рахул
источник
1
Это добавит новую строку. Вопрос в том, как обновить существующий.
Эдвард Брей