gpt4 book ai didi

c# - 使 AddOrUpdate 仅更改某些属性

转载 作者:太空狗 更新时间:2023-10-29 17:46:22 25 4
gpt4 key购买 nike

这可能是一个简单的问题,但我是 Code First 和迁移的新手,所以请多多包涵。我会将示例代码保持在最低限度以显示问题:

我有一个 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 方法中有这个,用于填写 CreatedOnLastModified 日期(简化):

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();
}

现在我有一个迁移,可以为一些用户提供种子,就像这样:

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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com