Answers:
Ladislav的答案已更新为使用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();
}
}
db.Entry(user).Property(x => x.Password).IsModified = true;
和不使用的名称空间db.Entry(user).Property("Password").IsModified = true;
db.Configuration.ValidateOnSaveEnabled = false;
,则可能希望继续验证要更新的字段:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
您可以通过以下方式告诉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语句来处理实际上是必需的那些更新。
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();
在寻找解决方案时,我通过Patrick Desjardins的博客在GONeale的答案中找到了一个变体:
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();
}
“ 如您所见,它将函数的表达式作为第二个参数。这将通过在Lambda表达式中指定要更新的属性来使用此方法。 ”
...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
(这里也提供了一些类似的解决方案:https : //stackoverflow.com/a/5749469/2115384)
我当前在自己的代码中使用的方法,已扩展为还处理type的(Linq)表达式ExpressionType.Convert
。在我的情况下,例如使用Guid
和其他对象属性,这是必需的。那些被“包装”在Convert()中,因此没有被处理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();
}
我在这里玩游戏迟到了,但这就是我的工作方式,我花了一段时间寻找一个令我满意的解决方案;UPDATE
当您通过“白名单”概念显式定义它们的含义时,这仅对已更改的字段产生一条语句,这反过来更安全地防止了Web表单注入。
我的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;
}
如果您愿意,可以将其包装在try..catch中,但是我个人希望调用者了解这种情况下的异常。
它将以这种方式调用(对我而言,这是通过ASP.NET Web API进行的):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
UpdateModel
命令所需的白名单一样),这样可以确保不会发生黑客表单注入并且它们无法更新不允许更新的字段。但是,如果有人可以将字符串数组转换为某种lambda表达式参数,然后在Update<T>
var entity=_context.Set<T>().Attach(item);
跟随它entity.Property(propertyName).IsModified = true;
应该起作用。
实体框架跟踪您对通过DbContext从数据库查询的对象所做的更改。例如,如果您的DbContext实例名称是dbContext
public void ChangePassword(int userId, string password){
var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
user.password = password;
dbContext.SaveChanges();
}
我知道这是一个旧线程,但我也在寻找类似的解决方案,因此决定使用提供的@ Doku-so解决方案。我评论要回答@Imran Rizvi提出的问题,我遵循了@ Doku-so链接,该链接显示了类似的实现。@Imran Rizvi的问题是,使用提供的解决方案“由于无法将Lambda表达式转换为Type'Expression> []',因为它不是委托类型”,他遇到了错误。我想对@ Doku-so的解决方案进行一些小的修改,以解决此错误,以防其他人遇到此帖子并决定使用@ Doku-so的解决方案。
问题是Update方法中的第二个参数,
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)
或者,如果您不想更改方法签名,则要调用Update方法,您需要添加' new '关键字,指定数组的大小,然后最后对每个属性使用collection object initializer语法进行更新,如下所示下面。
Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });
在@ Doku-so的示例中,他指定了一个表达式数组,因此您必须传递属性以在数组中进行更新,因为该数组还必须指定该数组的大小。为了避免这种情况,您还可以将expression参数更改为使用IEnumerable而不是数组。
这是我对@ 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();
}
用法:
this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);
@ Doku-so使用泛型方法提供了一种很酷的方法,我用这个概念解决了我的问题,但您只是不能按原样使用@ Doku-so的解决方案,在本帖子和链接的帖子中都没有人回答使用错误问题。
entityEntry.State = EntityState.Unchanged;
,参数中的所有更新值entityEntry
都将还原,因此未保存任何更改,请您帮忙,谢谢
在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
查找可确保已加载的实体不会触发SELECT,并且还会根据需要自动附加该实体(来自文档):
/// 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.
结合几个建议,我提出以下建议:
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;
我使用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();
}
注意自定义约定的用法,如果服务器中的属性为空,则该约定不会更新属性。
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);
查找此答案
您将不知道该属性是否被有意清除为null或只是没有任何值。换句话说,属性值只能替换为另一个值,而不能清除。
我一直在寻找相同的东西,最后我找到了解决方案
using (CString conn = new CString())
{
USER user = conn.USERs.Find(CMN.CurrentUser.ID);
user.PASSWORD = txtPass.Text;
conn.SaveChanges();
}
相信我,它像魅力一样对我有用。
这就是我使用的,使用自定义InjectNonNull(obj dest,obj src)使其完全灵活
[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;
}
}
Password
,你的意思是哈希密码,对不对?:-)