Как обновить только одно поле с использованием структуры сущности?

StackOverflow https://stackoverflow.com/questions/3642371

Вопрос

Вот стол

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

UserId
UserName
Password
EmailAddress

и код ..

public void ChangePassword(int userId, string password){
//code to update the password..
}
Это было полезно?

Решение

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

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

  • пойти на эфир в целом, в этом случае вы бы
    • загрузить объект на основе 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 и другие объектные свойства. Те были «обернуты» в преобразовании () и поэтому не обрабатываются 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();
}

В основном основной основе, Attach Возвращает запись, так что все, что вам нужно:

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

Я опоздал на игру здесь, но вот как я это делаю, я провел время охоты на решение, с которым я был удовлетворен; Это производит А.Н. UPDATE Заявление только для полей, которые изменяются, как вы явно определите, что они через концепцию «Белого списка», которая является более безопасной для предотвращения инъекций веб-формы в любом случае.

Выдержка из моего репозитория данных о утилизации:

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

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

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

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

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

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

Вы должны добавить ключевое слово «params» перед второй борьбой.

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

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

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

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

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

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

Основная структура отслеживает ваши изменения объектов, которые вы запросываете из базы данных через DBContext. Например, если имейте имя экземпляра DBContext DBContext

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}

В 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

Найти гарантирует, что уже загруженные объекты не вызывают выбор, а также автоматически присоединяет объект, если необходимо (из документов):

    ///     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.

я использую 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();
}

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

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

Значение инжектора V2.

Погляди Этот ответ

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

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

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

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

Поверьте мне, это работаю для меня, как очарование.

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

    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();
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top