Comment mettre à jour un seul champ en utilisant Entity Framework?
Voici la table
utilisateurs
UserId
UserName
Password
EmailAddress
et le code..
public void ChangePassword(int userId, string password){
//code to update the password..
}
13 réponses
Ladislav la réponse de mise à jour pour utiliser DbContext (introduit en 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();
}
}
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();
}
}
vous avez essentiellement deux options:
- allez de L'avant JUSQU'au bout, dans ce cas, vous
- chargez l'objet en fonction du
userId
fourni - l'objet entier est chargé - mettre à jour le "champ
password
1519130920" - sauvegardez l'objet en utilisant la méthode
.SaveChanges()
du contexte."
- chargez l'objet en fonction du
dans ce cas, c'est à EF comment gérer cela en détail. Je viens de tester cela, et dans le cas où je ne change qu'un seul champ d'un objet, ce que EF crée est à peu près ce que vous créeriez manuellement, aussi - quelque chose comme:
`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`
donc EF est assez intelligent pour comprendre quelles colonnes ont effectivement changé, et il va créer une déclaration T-SQL pour gérer seulement les mises à jour qui sont en fait nécessaires.
- vous définissez une procédure stockée qui fait exactement ce que vous besoin, dans le code T-SQL (il suffit de mettre à jour la colonne
Password
pour leUserId
donné et rien d'autre-exécute essentiellementUPDATE 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 faire les étapes décrites ci-dessus
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();
en cherchant une solution à ce problème, j'ai trouvé une variation sur la réponse de GONeale à travers le 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'un fonction. Cela permettra d'utiliser cette méthode en spécifiant dans un Lambda expression quelle propriété mettre à jour. "
...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
(un peu une solution similaire est également donnée ici: https://stackoverflow.com/a/5749469/2115384 )
la méthode que j'utilise actuellement dans mon propre code , étendu pour traiter aussi (Linq) des Expressions de type ExpressionType.Convert
. cela était nécessaire dans mon cas, par exemple avec Guid
et d'autres propriétés de l'objet. Ceux-ci ont été "enveloppés" dans un Convert() et ne sont donc pas manipulé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();
}
je suis en retard pour le jeu ici, mais c'est comme ça que je le fais, j'ai passé un moment à chercher une solution qui m'a rassasié; cela produit une déclaration UPDATE
uniquement pour les champs qui sont modifiés, car vous définissez explicitement ce qu'ils sont à travers un concept de" liste blanche " qui est plus sûr pour empêcher l'injection de forme de web de toute façon.
un extrait de mon dépôt 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 enveloppé dans un essai..catch si vous l'avez souhaité, mais j'aime personnellement que mon interlocuteur soit au courant des exceptions dans ce scénario.
il serait appelé dans quelque chose comme cette façon (pour moi, c'était via un ASP.NET API Web):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
dans Entity Framework Core, Attach
renvoie l'entrée, donc tout ce dont vous avez besoin est:
var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
Entity framework suit vos modifications sur les objets que vous avez demandés à partir de la 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();
}
je sais que c'est un vieux fil, mais je cherchais aussi une solution similaire et j'ai décidé d'aller avec la solution @Doku-ainsi prévu. 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 " ne peut pas convertir L'expression Lambda en Type 'Expression> [] 'parce que ce n'est pas un type de délégué'. Je voulais offrir une petite modification que j'ai faite La solution de @Doku-so qui corrige cette erreur au cas où quelqu'un d'autre tomberait sur ce post et déciderait d'utiliser la solution de @Doku-so.
Le problème est que le deuxième argument de la méthode de mise à Jour,
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 deuxième argument comme tel.
public int Update(T entity, params Expression<Func<T, object>>[] properties)
ou si vous ne voulez pas changer la méthode signature puis pour appeler la méthode de mise à jour, vous devez ajouter le ' nouveau ' mot-clé, spécifier la taille du tableau, puis finalement utiliser la syntaxe collection objet initialiseur pour chaque propriété à mettre à jour comme vu 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 de sorte que vous devez passer les propriétés à mettre à jour dans un tableau, en raison du tableau vous devez également spécifier la taille du tableau. Éviter ceci vous pouvez également changer l'argument d'expression pour utiliser 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 fourni une approche cool en utilisant generic's, j'ai utilisé le concept pour résoudre mon problème, mais vous ne pouvez tout simplement pas utiliser @Doku-so la solution comme elle est et à la fois dans ce post et le lien post no répondit l'erreur d'utilisation questions.
j'utilise ValueInjecter
nuget pour injecter de Liaison de Modèle dans la base de données de l'Entité à l'aide suivante:
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();
}
Remarque l'utilisation de la convention personnalisée qui ne met pas à jour les propriétés si elles sont nulles 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);
}
}
Utilisation:
target.InjectFrom<NoNullsInjection>(source);
Valeur Injecteur V2
Recherche cette réponse
Avertissement
vous ne saurez pas si la propriété est intentionnellement autorisée à null ou si elle n'a tout simplement pas de valeur. En d'autres termes, la valeur de la propriété ne peut être remplacé par une autre valeur, mais pas effacé.
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;
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();
}
}