Respostas:
Resposta de Ladislav atualizada para usar o DbContext (introduzido no 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;
e nãodb.Entry(user).Property("Password").IsModified = true;
db.Configuration.ValidateOnSaveEnabled = false;
poderá continuar validando o campo que está atualizando: #if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Você pode informar ao EF quais propriedades devem ser atualizadas desta maneira:
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();
}
}
Você tem basicamente duas opções:
userId
fornecido - o objeto inteiro é carregadopassword
campo.SaveChanges()
método do contextoNesse caso, cabe à EF como lidar com isso em detalhes. Acabei de testar isso e, no caso de alterar apenas um único campo de um objeto, o que o EF cria é basicamente o que você criaria manualmente também - algo como:
`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`
Portanto, a EF é inteligente o suficiente para descobrir quais colunas realmente mudaram e criará uma instrução T-SQL para lidar apenas com as atualizações necessárias.
Password
coluna para o dado UserId
e nada mais - basicamente é executado UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId
) e cria uma importação de função para esse procedimento armazenado no seu modelo EF e você chama isso em vez de executar as etapas descritas acimaNo Entity Framework Core, Attach
retorna a entrada, então tudo que você precisa é:
var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
eu estou usando isso:
entidade:
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; }
}
código do acessador:
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();
Enquanto procurava uma solução para esse problema, encontrei uma variação na resposta de GONeale no 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();
}
" Como você pode ver, assume como segundo parâmetro uma expressão de uma função. Isso permitirá usar esse método especificando em uma expressão Lambda qual propriedade atualizar. "
...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
(Uma solução semelhante é também fornecida aqui: https://stackoverflow.com/a/5749469/2115384 )
O método que estou usando atualmente no meu próprio código , estendido para manipular também (Linq) expressões do tipo ExpressionType.Convert
. Isso foi necessário no meu caso, por exemplo, com Guid
e outras propriedades do objeto. Esses foram 'agrupados' em um Convert () e, portanto, não são tratados por 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();
}
Estou atrasado para o jogo aqui, mas é assim que faço, passei um tempo procurando uma solução que me satisfizesse; isso produz umUPDATE
APENAS declaração para os campos alterados, à medida que você define explicitamente o que eles são por meio de um conceito de "lista branca" que é mais seguro para impedir a injeção de formulários da Web de qualquer maneira.
Um trecho do meu repositório de dados 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;
}
Isso pode ser envolvido em uma tentativa ... captura, se você desejar, mas eu pessoalmente gosto que meu interlocutor saiba das exceções nesse cenário.
Seria chamado assim: (para mim, isso foi por meio de uma API da Web do ASP.NET):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
UpdateModel
comando do ASP.NET MVC ), para garantir que a injeção de formulários de hackers não ocorra e que eles não possam atualizar os campos que não podem atualizar. Se, no entanto, alguém puder converter o array de strings para algum tipo de parâmetro de expressões lambda e trabalhar com ele no Update<T>
, ótimo
var entity=_context.Set<T>().Attach(item);
seguida pelo entity.Property(propertyName).IsModified = true;
loop deve funcionar.
A estrutura de entidades rastreia suas alterações nos objetos que você consultou do banco de dados via DbContext. Por exemplo, se o nome da instância DbContext for dbContext
public void ChangePassword(int userId, string password){
var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
user.password = password;
dbContext.SaveChanges();
}
Eu sei que esse é um tópico antigo, mas eu também estava procurando uma solução semelhante e decidi seguir com a solução @ Doku-so fornecida. Estou comentando para responder à pergunta feita por @Imran Rizvi, segui o link @ Doku-so, que mostra uma implementação semelhante. A pergunta de @Imran Rizvi foi que ele estava recebendo um erro usando a solução fornecida 'Não é possível converter a expressão Lambda para o tipo' Expressão> [] 'porque não é um tipo de delegado'. Eu queria oferecer uma pequena modificação que fiz na solução da @ Doku-so que corrige esse erro, caso alguém se depare com este post e decida usar a solução da @ Doku-so.
O problema é o segundo argumento no método Update,
public int Update(T entity, Expression<Func<T, object>>[] properties).
Para chamar esse método usando a sintaxe fornecida ...
Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
Você deve adicionar a palavra-chave 'params' na frente da segunda versão assim.
public int Update(T entity, params Expression<Func<T, object>>[] properties)
ou, se você não quiser alterar a assinatura do método, para chamar o método Update, adicione a palavra-chave ' new ', especifique o tamanho da matriz e, finalmente, use a sintaxe do inicializador do objeto de coleção para cada propriedade a ser atualizada, como visto abaixo.
Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });
No exemplo do @ Doku-so, ele está especificando uma matriz de Expressões, portanto você deve passar as propriedades para atualizar em uma matriz, por causa da matriz, você também deve especificar o tamanho da matriz. Para evitar isso, você também pode alterar o argumento da expressão para usar IEnumerable em vez de uma matriz.
Aqui está minha implementação da solução do @ 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();
}
Uso:
this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);
@ Doku-so forneceu uma abordagem legal usando genéricos, usei o conceito para resolver meu problema, mas você simplesmente não pode usar a solução @ Doku-so como está e, tanto neste post como no post vinculado, ninguém respondeu às perguntas sobre erros de uso.
entityEntry.State = EntityState.Unchanged;
todos os valores atualizados em parâmetro entityEntry
get revert, por isso há alterações são salvas, você pode por favor me ajude sobre ele, graças
No EntityFramework Core 2.x, não há necessidade de Attach
:
// get a tracked entity
var entity = context.User.Find(userId);
entity.someProp = someValue;
// other property changes might come here
context.SaveChanges();
Tentei isso no SQL Server e crie um perfil:
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
O Find garante que as entidades já carregadas não acionem um SELECT e também anexa automaticamente a entidade, se necessário (nos documentos):
/// 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.
Combinando várias sugestões, proponho o seguinte:
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;
}
}
chamado por
UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);
Ou pela
await UpdateDbEntryAsync(dbc, d => d.Property1);
Ou pela
bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;
Eu uso o ValueInjecter
nuget para injetar o Modelo de Ligação na Entidade de banco de dados usando o seguinte:
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();
}
Observe o uso da convenção personalizada que não atualiza as Propriedades se elas forem nulas do servidor.
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);
}
}
Uso:
target.InjectFrom<NoNullsInjection>(source);
Procure esta resposta
Você não saberá se a propriedade foi intencionalmente limpa para nula OU se simplesmente não tinha nenhum valor. Em outras palavras, o valor da propriedade só pode ser substituído por outro valor, mas não limpo.
Eu estava procurando o mesmo e finalmente encontrei a solução
using (CString conn = new CString())
{
USER user = conn.USERs.Find(CMN.CurrentUser.ID);
user.PASSWORD = txtPass.Text;
conn.SaveChanges();
}
acredite em mim, funciona para mim como um encanto.
É isso que eu uso, usando o InjectNonNull personalizado (obj dest, obj src), que o torna totalmente flexível
[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
, você quer dizer senha com hash, certo? :-)