Réponses:
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();
}
}
db.Entry(user).Property(x => x.Password).IsModified = true;
et nondb.Entry(user).Property("Password").IsModified = true;
db.Configuration.ValidateOnSaveEnabled = false;
vous voudrez peut-être continuer à valider le champ que vous mettez à jour:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
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:
userId
fourni - l'objet entier est chargépassword
champ.SaveChanges()
méthode du contexteDans 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éez manuellement aussi - quelque chose comme:
`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`
Ainsi, EF est suffisamment intelligent pour déterminer quelles colonnes ont effectivement changé, et il créera une instruction T-SQL pour gérer uniquement les mises à jour qui sont en fait nécessaires.
Password
colonne pour le donné UserId
et rien d'autre - s'exécute essentiellement 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 ceci fonction au lieu de suivre les étapes décrites ci-dessusDans 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();
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 variante de la réponse de GONeale sur 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 deuxième paramètre l'expression d'une fonction. Cela permettra d'utiliser cette méthode en spécifiant dans une expression Lambda la propriété à mettre à jour. "
...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
(Une solution quelque 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. Celles-ci ont été «enveloppées» dans un Convert () et donc non gérées 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 au jeu ici, mais c'est comme ça que je le fais, j'ai passé un certain temps à chercher une solution dont j'étais satisfait; cela produit une UPDATE
instruction UNIQUEMENT pour les champs qui sont modifiés, car vous définissez explicitement ce qu'ils sont via un concept de «liste blanche» qui est plus sûr pour empêcher l'injection de formulaire Web de toute façon.
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 enveloppé dans un try..catch si vous le souhaitez, mais j'aime personnellement que mon interlocuteur connaisse les exceptions de ce scénario.
Il serait appelé de cette façon (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));
UpdateModel
commande d' ASP.NET MVC ), de cette façon vous vous assurez que l'injection de formulaire de pirate ne peut pas se produire et qu'ils ne peuvent pas mettre à jour les champs qu'ils ne sont pas autorisés à mettre à jour. Si toutefois quelqu'un peut convertir le tableau de chaînes en une sorte de paramètre d'expressions lambda et travailler avec lui dans le Update<T>
, super
var entity=_context.Set<T>().Attach(item);
suivie par entity.Property(propertyName).IsModified = true;
dans la boucle devrait fonctionner.
Le cadre d'entité suit vos modifications sur les objets que vous avez interrogé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 également une solution similaire et j'ai décidé d'aller avec 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 l'expression Lambda en type' Expression> [] 'car ce n'est pas un type délégué'. Je voulais proposer une petite modification que j'ai apportée à la solution de @ Doku-so qui corrige cette erreur au cas où quelqu'un d'autre rencontrerait ce message et déciderait d'utiliser la solution de @ Doku-so.
Le problème 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 deuxième argument comme tel.
public int Update(T entity, params Expression<Func<T, object>>[] properties)
ou si vous ne voulez pas changer la signature de la méthode, pour appeler la méthode Update, vous devez ajouter le mot-clé ' new ', spécifier la taille du tableau, puis enfin utiliser la syntaxe d'initialisation d'objet de collection pour chaque propriété à mettre à jour comme indiqué au dessous de.
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 à mettre à jour dans un tableau, à cause du tableau, vous devez également spécifier la taille du tableau. Pour éviter cela, vous pouvez également modifier l'argument de l'expression pour utiliser IEnumerable au lieu d'un tableau.
Voici ma mise en œuvre 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();
}
Usage:
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 tout simplement pas utiliser la solution de @ Doku-so telle quelle et dans cet article et dans l'article lié, personne n'a répondu aux questions sur les erreurs d'utilisation.
entityEntry.State = EntityState.Unchanged;
toutes les valeurs mises à jour dans le paramètre entityEntry
sont annulées, donc aucune modification n'est enregistrée, pouvez-vous s'il vous plaît aider, merci
Dans EntityFramework Core 2.x, il n'est pas nécessaire de Attach
:
// get a tracked entity
var entity = context.User.Find(userId);
entity.someProp = someValue;
// other property changes might come here
context.SaveChanges();
J'ai essayé cela dans SQL Server et en le profilant:
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 garantit que les entités déjà chargées ne déclenchent pas de SELECT et attache également automatiquement l'entité 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.
En combinant plusieurs suggestions, je propose les suivantes:
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;
J'utilise ValueInjecter
nuget pour injecter le modèle de liaison dans l'entité de base de données en utilisant ce qui suit:
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 d'une convention personnalisée qui ne met pas à jour les propriétés si elles sont nulles à partir du serveur.
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);
Recherchez cette réponse
Vous ne saurez pas si la propriété est intentionnellement effacée à null OU si elle n'a simplement aucune valeur. En d'autres termes, la valeur de propriété ne peut être remplacée que par une autre valeur mais pas effacée.
Je cherchais la même chose et j'ai finalement 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.
C'est ce que j'utilise, en utilisant InjectNonNull personnalisé (obj dest, obj src), cela le rend totalement flexible
[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;
}
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();
}
}
Password
, vous voulez dire un mot de passe haché, non? :-)