web-dev-qa-db-fra.com

Comment mettre à jour un seul champ à l'aide d'Entity Framework?

Voici la table

tilisateurs

UserId
UserName
Password
EmailAddress

et le code ..

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

La réponse de Ladislav a été mise à jour pour utiliser DbContext (introduit dans 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();
  }
}
339
Stuart

Vous pouvez indiquer à EF quelles propriétés doivent être mises à jour de cette manière:

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

Vous avez essentiellement deux options:

  • parcourez le trajet EF jusqu'au bout. Dans ce cas, vous
    • charge l'objet en fonction de la userId fournie - l'objet entier est chargé
    • mettre à jour le champ password
    • sauvegarder l'objet en utilisant la méthode .SaveChanges() du contexte

Dans ce cas, il appartient à EF de gérer cela en détail. Je viens de tester cela, et dans le cas où je ne modifie qu'un seul champ d'un objet, ce que EF crée correspond à peu près à ce que vous créez manuellement: quelque chose comme:

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

EF est donc assez intelligent pour déterminer quelles colonnes ont bien été modifiées et il créera une instruction T-SQL pour gérer uniquement les mises à jour réellement nécessaires.

  • vous définissez une procédure stockée qui fait exactement ce dont vous avez besoin, dans le code T-SQL (il suffit de mettre à jour la colonne Password pour la variable UserId et rien d’autre - exécute en gros UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) et vous créez une importation de fonction pour cette procédure stockée dans votre modèle EF et vous appelez cette fonction au lieu de suivre les étapes décrites ci-dessus
15
marc_s

j'utilise ceci:

entité:

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

code d'accès:

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

En cherchant une solution à ce problème, j'ai trouvé une variation de la réponse de GONeale à travers blog de Patrick Desjardins :

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

" Comme vous pouvez le voir, il prend comme second paramètre une expression d'une fonction. Cela permettra d'utiliser cette méthode en spécifiant dans une expression Lambda quelle propriété mettre à jour. "

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

(Une solution un peu similaire est également donnée ici: https://stackoverflow.com/a/5749469/2115384 )

La méthode que j'utilise actuellement dans mon propre code , étendue pour gérer également les expressions (Linq) de type ExpressionType.Convert. Cela était nécessaire dans mon cas, par exemple avec Guid et d'autres propriétés d'objet. Ceux qui ont été 'emballés' dans un fichier Convert () et ne sont donc pas gérés par 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();
}
8
Doku-so

Dans Entity Framework Core, Attach renvoie l'entrée. Il suffit donc de:

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

Je suis en retard au jeu ici, mais c’est comme cela que je le fais. J'ai passé un certain temps à chercher une solution qui me satisfaisait; cela produit une instruction UPDATE UNIQUEMENT pour les champs modifiés, car vous définissez explicitement ce qu’ils sont au moyen d’un concept de "liste blanche" qui est plus sûr pour empêcher l’injection de formulaires Web.

Un extrait de mon référentiel de données 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;
}

Cela pourrait être emballé dans un try..catch si vous le souhaitiez, mais personnellement, j'aime bien que mon correspondant connaisse les exceptions de ce scénario.

Cela s'appellerait de cette manière (pour moi, c'était via une API Web ASP.NET):

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

Je sais que c'est un vieux fil, mais je cherchais également une solution similaire et j'ai décidé de choisir la solution @ Doku-so fournie. Je commente pour répondre à la question posée par @Imran Rizvi. J'ai suivi le lien @ Doku-so qui montre une implémentation similaire. La question de @Imran Rizvi était qu'il obtenait une erreur en utilisant la solution fournie 'Impossible de convertir une expression Lambda en Type' Expression> [] 'car il ne s'agit pas d'un type de délégué'. Je voulais proposer une petite modification apportée à la solution de @ Doku-so, qui corrige cette erreur si quelqu'un d'autre découvrait ce message et décidait d'utiliser la solution de @ Doku-so.

La question est le deuxième argument de la méthode Update,

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

Pour appeler cette méthode en utilisant la syntaxe fournie ...

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

Vous devez ajouter le mot clé 'params' devant le second argument.

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

ou si vous ne souhaitez pas modifier la signature de la méthode, puis pour appeler la méthode Update, vous devez ajouter le signe ' new 'mot-clé, spécifiez la taille du tableau, puis utilisez finalement la syntaxe d'initialiseur d'objet de collection pour chaque propriété à mettre à jour, comme indiqué ci-dessous.

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

Dans l'exemple de @ Doku-so, il spécifie un tableau d'expressions. Vous devez donc transmettre les propriétés pour la mise à jour dans un tableau. En raison du tableau, vous devez également spécifier la taille du tableau. Pour éviter cela, vous pouvez également changer l'argument d'expression pour qu'il utilise IEnumerable au lieu d'un tableau.

Voici mon implémentation de la solution de @ 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();
    }

Utilisation:

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

@ Doku-so a fourni une approche intéressante en utilisant des génériques. J'ai utilisé le concept pour résoudre mon problème, mais vous ne pouvez pas utiliser la solution de @ Doku-so en l'état. Dans cet article et dans l'article lié, personne n'a répondu aux questions d'erreur d'utilisation.

3
null

Entity Framework suit vos modifications sur les objets que vous avez interrogés depuis une base de données via DbContext. Par exemple, si votre nom d'instance DbContext est dbContext

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

Dans EntityFramework Core 2.x, il n'est pas nécessaire d'utiliser Attach:

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

Essayé cela dans SQL Server et le profiler:

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

La recherche permet de s’assurer que les entités déjà chargées ne déclenchent pas un SELECT et l’attache automatiquement si nécessaire (à partir de la documentation):

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

Je cherchais pareil et finalement j'ai trouvé la solution

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

croyez-moi cela fonctionne pour moi comme un charme.

1

J'utilise ValueInjecter nuget pour injecter le modèle de liaison dans la base de données Entity en utilisant les éléments suivants:

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

Notez l'utilisation de la convention personnalisée qui ne met pas à jour les propriétés si elles sont null du serveur.

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

Usage:

target.InjectFrom<NoNullsInjection>(source);

Value Injecter V2

Recherche cette réponse

Caveat

Vous ne saurez pas si la propriété est intentionnellement effacée à null OR elle n'a simplement aucune valeur. En d'autres termes, la valeur de la propriété ne peut être remplacée que par une autre valeur, mais pas effacée.

1
Korayem

En combinant plusieurs suggestions, je propose ce qui suit:

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

appelé par

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

Ou par

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

Ou par

bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;
0
Guy