这可能是一个简单的问题,但是我是Code First和Migrations的新手,所以请多多包涵。我将示例代码减至最少以显示该问题:
我有一个BaseAuditableEntity
,其中包括此代码(除其他事项外,让我们简化一下):
public abstract class BaseAuditableEntity : BaseEntity, IAuditableEntity
{
public DateTime CreatedOn { get; set; }
public DateTime LastModified { get; set; }
}
现在,(例如)
User
POCO继承自它:public class User : BaseAuditableEntity
{
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public bool Active { get; set; }
public DateTime? LastLogin { get; set; }
}
我在上下文的
SaveChanges
方法中具有此名称,以填写CreatedOn
和LastModified
日期(简化):public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditableEntity>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(p => p.State != EntityState.Unchanged))
{
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
entry.Entity.CreatedOn = now;
entry.Entity.LastModified = now;
}
}
return base.SaveChanges();
}
现在,我已经进行了迁移,从而为一些用户注入(inject)了种子,如下所示:
protected override void Seed(MyContext context)
{
context.Users.AddOrUpdate(
p => p.UserName,
new User { Active = true,
FullName = "My user name",
UserName = "ThisUser",
PasswordHash = "",
Email = "my@email",
LastLogin = null,
}
// etc.
);
}
现在,我在迁移后使用
AddOrUpdate
进行播种时遇到了问题。当实体是新实体(正在添加)时,CreatedOn
会正确填充,并且一切都会按预期进行。但是,当实体被修改时(它已经存在于数据库中并且与UserName
匹配),它将尝试使用我正在创建的新实体对其进行更新...此操作失败,因为CreatedOn
包含无效的DateTime
(在这种情况下为DateTime.MinValue
)。有什么方法可以使用
AddOrUpdate
方法,以便它实际上从数据库中检索匹配的实体并只是更新非默认字段?还是可以通过某种方式告诉它哪些字段不更新?对于这种特定情况,我希望CreatedOn
字段保持不变,但是希望您能有一个通用的解决方案。也许我应该做自己的
AddOrUpdate
方法,该方法包括带有要更改的字段的谓词,而不是将其传递给一个全新的实体?这是EF 6.1
更新
我知道我可以轻松解决
CreatedOn
日期,这是我目前针对此特定情况所采取的措施:foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entry.Entity.CreatedOn = now;
}
else
{
if (entry.Property(p => p.CreatedOn).CurrentValue == DateTime.MinValue)
{
var original = entry.Property(p => p.CreatedOn).OriginalValue;
entry.Property(p => p.CreatedOn).CurrentValue = original != SqlDateTime.MinValue ? original : now;
entry.Property(p => p.CreatedOn).IsModified = true;
}
}
entry.Entity.LastModified = now;
}
我正在寻找更通用的解决方案
最佳答案
AddOrUpdate
的实现使用 CurrentValues.SetValues
,以便所有标量属性都将被修改。
我已经扩展了功能,使其可以接受要在更新时进行修改的属性,否则,它是一种创建,只需使用 DbSet<T>::Add
即可。
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public static class SeedExtension
{
public static void Upsert<T>(this DbContext db, Expression<Func<T, object>> identifierExpression, Expression<Func<T, object>> updatingExpression, params T[] entities)
where T : class
{
if (updatingExpression == null)
{
db.Set<T>().AddOrUpdate(identifierExpression, entities);
return;
}
var identifyingProperties = GetProperties<T>(identifierExpression).ToList();
Debug.Assert(identifyingProperties.Count != 0);
var updatingProperties = GetProperties<T>(updatingExpression).Where(pi => IsModifiedable(pi.PropertyType)).ToList();
Debug.Assert(updatingProperties.Count != 0);
var parameter = Expression.Parameter(typeof(T));
foreach (var entity in entities)
{
var matches = identifyingProperties.Select(pi => Expression.Equal(Expression.Property(parameter, pi.Name), Expression.Constant(pi.GetValue(entity, null))));
var matchExpression = matches.Aggregate<BinaryExpression, Expression>(null, (agg, v) => (agg == null) ? v : Expression.AndAlso(agg, v));
var predicate = Expression.Lambda<Func<T, bool>>(matchExpression, new[] { parameter });
var existing = db.Set<T>().SingleOrDefault(predicate);
if (existing == null)
{
// New.
db.Set<T>().Add(entity);
continue;
}
// Update.
foreach (var prop in updatingProperties)
{
var oldValue = prop.GetValue(existing, null);
var newValue = prop.GetValue(entity, null);
if (Equals(oldValue, newValue)) continue;
db.Entry(existing).Property(prop.Name).IsModified = true;
prop.SetValue(existing, newValue);
}
}
}
private static bool IsModifiedable(Type type)
{
return type.IsPrimitive || type.IsValueType || type == typeof(string);
}
private static IEnumerable<PropertyInfo> GetProperties<T>(Expression<Func<T, object>> exp) where T : class
{
Debug.Assert(exp != null);
Debug.Assert(exp.Body != null);
Debug.Assert(exp.Parameters.Count == 1);
var type = typeof(T);
var properties = new List<PropertyInfo>();
if (exp.Body.NodeType == ExpressionType.MemberAccess)
{
var memExp = exp.Body as MemberExpression;
if (memExp != null && memExp.Member != null)
properties.Add(type.GetProperty(memExp.Member.Name));
}
else if (exp.Body.NodeType == ExpressionType.Convert)
{
var unaryExp = exp.Body as UnaryExpression;
if (unaryExp != null)
{
var propExp = unaryExp.Operand as MemberExpression;
if (propExp != null && propExp.Member != null)
properties.Add(type.GetProperty(propExp.Member.Name));
}
}
else if (exp.Body.NodeType == ExpressionType.New)
{
var newExp = exp.Body as NewExpression;
if (newExp != null)
properties.AddRange(newExp.Members.Select(x => type.GetProperty(x.Name)));
}
return properties.OfType<PropertyInfo>();
}
}
用法。
context.Upsert(
p => p.UserName,
p => new { p.Active, p.FullName, p.Email },
new User
{
Active = true,
FullName = "My user name",
UserName = "ThisUser",
Email = "my@email",
}
);
关于c# - 使AddOrUpdate仅更改某些属性,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25928353/