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

стол

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

UserId
UserName
Password
EmailAddress

и код..

public void ChangePassword(int userId, string password){
//code to update the password..
}

13 ответов


ответ Ладислава обновлен для использования 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();
  }
}

вы можете сказать 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();
  }
}

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

  • пройдите путь EF полностью, в этом случае вы бы
    • загрузите объект на основе 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, и вы вызываете эту функцию вместо выполнения описанных выше шагов

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

сущности:

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

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

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

Я опаздываю на игру здесь, но вот как я это делаю, я провел некоторое время в поисках решения, которым я был сыт; это производит 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;
}

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

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

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));

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

var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();

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

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

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

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(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });

в Примере @Doku-so он указывает массив выражений, поэтому вы должны передать свойства для обновления в массиве, из-за массива вы также должны указать размер массива. Чтобы избежать этого, вы также можете изменить аргумент expression для использования 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-таким образом, при условии прохладного подхода с использованием generic, я использовал концепцию для решения моей проблемы, но вы просто не можете использовать решение @Doku-so, как есть, и в этом посте и связанном посте никто не ответил на вопросы об ошибках использования.


Я использую ValueInjecter nuget для внедрения модели привязки в объект базы данных, используя следующее:

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

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

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 П2

Поиск ответ

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

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


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

    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;

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

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