Entity framework core EF 7/Core中的AddOrUpdate发生了什么?

Entity framework core EF 7/Core中的AddOrUpdate发生了什么?,entity-framework-core,Entity Framework Core,我正在使用EntityFramework.Core 7.0.0-rc1-final编写一个种子方法 DbSet的AddOrUpdate方法发生了什么变化?它正在等待实现。请参阅问题& 更新:根据下面的评论(未经验证)-此功能最终将在.NETCore2.1中发布 您可以使用我创建的扩展方法修补代码库,以便迁移到EF Core: public static void AddOrUpdate<T>(this DbSet<T> dbSet, T data) where T

我正在使用EntityFramework.Core 7.0.0-rc1-final编写一个种子方法


DbSet的AddOrUpdate方法发生了什么变化?

它正在等待实现。请参阅问题&


更新:根据下面的评论(未经验证)-此功能最终将在.NETCore2.1中发布

您可以使用我创建的扩展方法修补代码库,以便迁移到EF Core:

   public static void AddOrUpdate<T>(this DbSet<T> dbSet, T data) where T : class
        {
            var t = typeof(T);
            PropertyInfo keyField = null;
            foreach (var propt in t.GetProperties())
            {
                var keyAttr = propt.GetCustomAttribute<KeyAttribute>();
                if (keyAttr != null)
                {
                    keyField = propt;
                    break; // assume no composite keys
                }
            }
            if (keyField == null)
            {
                throw new Exception($"{t.FullName} does not have a KeyAttribute field. Unable to exec AddOrUpdate call.");
            }
            var keyVal = keyField.GetValue(data);
            var dbVal = dbSet.Find(keyVal);
            if (dbVal != null)
            {
                dbSet.Update(data);
                return;
            }
            dbSet.Add(data);
        }
publicstaticvoidaddorupdate(此DbSet DbSet,T数据),其中T:class
{
var t=类型(t);
PropertyInfo关键字字段=null;
foreach(t.GetProperties()中的var propt)
{
var keyAttr=propt.GetCustomAttribute();
if(keyAttr!=null)
{
keyField=propt;
break;//假定没有组合键
}
}
if(keyField==null)
{
抛出新异常($“{t.FullName}没有KeyAttribute字段。无法执行AddOrUpdate调用。”);
}
var keyVal=keyField.GetValue(数据);
var dbVal=dbSet.Find(keyVal);
if(dbVal!=null)
{
更新(数据);
返回;
}
添加(数据);
}

我想这就是你想要的

public static class DbSetExtension
{
    public static void AddOrUpdate<T>(this DbSet<T> dbSet, T data) where T : class
    {
        var context = dbSet.GetContext();
        var ids = context.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(x => x.Name);

        var t = typeof(T);
        List<PropertyInfo> keyFields = new List<PropertyInfo>();

        foreach (var propt in t.GetProperties())
        {
            var keyAttr = ids.Contains(propt.Name);
            if (keyAttr)
            {
                keyFields.Add(propt);
            }
        }
        if (keyFields.Count <= 0)
        {
            throw new Exception($"{t.FullName} does not have a KeyAttribute field. Unable to exec AddOrUpdate call.");
        }
        var entities = dbSet.AsNoTracking().ToList();
        foreach (var keyField in keyFields)
        {
            var keyVal = keyField.GetValue(data);
            entities = entities.Where(p => p.GetType().GetProperty(keyField.Name).GetValue(p).Equals(keyVal)).ToList();
        }
        var dbVal = entities.FirstOrDefault();
        if (dbVal != null)
        {
            context.Entry(dbVal).CurrentValues.SetValues(data);
            context.Entry(dbVal).State = EntityState.Modified;
            return;
        }
        dbSet.Add(data);
    }

    public static void AddOrUpdate<T>(this DbSet<T> dbSet, Expression<Func<T, object>> key, T data) where T : class
    {
        var context = dbSet.GetContext();
        var ids = context.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(x => x.Name);
        var t = typeof(T);
        var keyObject = key.Compile()(data);
        PropertyInfo[] keyFields = keyObject.GetType().GetProperties().Select(p=>t.GetProperty(p.Name)).ToArray();
        if (keyFields == null)
        {
            throw new Exception($"{t.FullName} does not have a KeyAttribute field. Unable to exec AddOrUpdate call.");
        }
        var keyVals = keyFields.Select(p => p.GetValue(data));
        var entities = dbSet.AsNoTracking().ToList();
        int i = 0;
        foreach (var keyVal in keyVals)
        {
            entities = entities.Where(p => p.GetType().GetProperty(keyFields[i].Name).GetValue(p).Equals(keyVal)).ToList();
            i++;
        }
        if (entities.Any())
        {
            var dbVal = entities.FirstOrDefault();
            var keyAttrs =
                data.GetType().GetProperties().Where(p => ids.Contains(p.Name)).ToList();
            if (keyAttrs.Any())
            {
                foreach (var keyAttr in keyAttrs)
                {
                    keyAttr.SetValue(data,
                        dbVal.GetType()
                            .GetProperties()
                            .FirstOrDefault(p => p.Name == keyAttr.Name)
                            .GetValue(dbVal));
                }
                context.Entry(dbVal).CurrentValues.SetValues(data);
                context.Entry(dbVal).State = EntityState.Modified;
                return;
            }                
        }
        dbSet.Add(data);
    }
}

public static class HackyDbSetGetContextTrick
{
    public static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet)
        where TEntity : class
    {
        return (DbContext)dbSet
            .GetType().GetTypeInfo()
            .GetField("_context", BindingFlags.NonPublic | BindingFlags.Instance)
            .GetValue(dbSet);
    }
}
公共静态类DbSetExtension
{
公共静态void AddOrUpdate(此DbSet DbSet,T data),其中T:class
{
var context=dbSet.GetContext();
var id=context.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(x=>x.Name);
var t=类型(t);
List keyFields=新列表();
foreach(t.GetProperties()中的var propt)
{
var keyAttr=ids.Contains(propt.Name);
if(keyAttr)
{
keyFields.Add(propt);
}
}
if(keyFields.Count p.GetType().GetProperty(keyField.Name).GetValue(p).Equals(keyVal)).ToList();
}
var dbVal=entities.FirstOrDefault();
if(dbVal!=null)
{
context.Entry(dbVal).CurrentValues.SetValues(数据);
context.Entry(dbVal.State=EntityState.Modified;
返回;
}
添加(数据);
}
公共静态void AddOrUpdate(此DbSet DbSet,表达式键,T数据),其中T:class
{
var context=dbSet.GetContext();
var id=context.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(x=>x.Name);
var t=类型(t);
var keyObject=key.Compile()(数据);
PropertyInfo[]keyFields=keyObject.GetType().GetProperties().Select(p=>t.GetProperty(p.Name)).ToArray();
if(keyFields==null)
{
抛出新异常($“{t.FullName}没有KeyAttribute字段。无法执行AddOrUpdate调用。”);
}
var-keyVals=keyFields.Select(p=>p.GetValue(数据));
var entities=dbSet.AsNoTracking().ToList();
int i=0;
foreach(在keyVals中的var keyVal)
{
entities=entities.Where(p=>p.GetType().GetProperty(keyFields[i].Name).GetValue(p).Equals(keyVal)).ToList();
i++;
}
if(entities.Any())
{
var dbVal=entities.FirstOrDefault();
var-keyAttrs=
data.GetType().GetProperties().Where(p=>ids.Contains(p.Name)).ToList();
if(keyAttrs.Any())
{
foreach(keyAttrs中的var keyAttr)
{
keyAttr.SetValue(数据,
dbVal.GetType()
.GetProperties()
.FirstOrDefault(p=>p.Name==keyAttr.Name)
.GetValue(dbVal));
}
context.Entry(dbVal).CurrentValues.SetValues(数据);
context.Entry(dbVal.State=EntityState.Modified;
返回;
}                
}
添加(数据);
}
}
公共静态类HackyDbSetGetContextTrick
{
公共静态DbContext GetContext(此DbSet DbSet)
地点:班级
{
返回(DbContext)dbSet
.GetType().GetTypeInfo()
.GetField(“_context”,BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(dbSet);
}
}

我认为,如果假设基本实体类是一个合法选项,那么这个解决方案是这个问题的一个更简单的解决方案。简单性来自于实现DomainEntityBase的域实体,这减轻了其他建议解决方案中的许多复杂性

public static class DbContextExtensions
{
    public static void AddOrUpdate<T>(this DbSet<T> dbSet, IEnumerable<T> records) 
        where T : DomainEntityBase
    {
        foreach (var data in records)
        {
            var exists = dbSet.AsNoTracking().Any(x => x.Id == data.Id);
            if (exists)
            {
                dbSet.Update(data);
                continue;
            }
            dbSet.Add(data);
        }
    }
}

public class DomainEntityBase
{
    [Key]
    public Guid Id { get; set; }
}
公共静态类DbContextensions
{
公共静态void AddOrUpdate(此DbSet DbSet,IEnumerable记录)
其中T:DomainEntityBase
{
foreach(记录中的var数据)
{
var exists=dbSet.AsNoTracking().Any(x=>x.Id==data.Id);
如果(存在)
{
更新(数据);
继续;
}
添加(数据);
}
}
}
公共类DomainEntityBase
{
[关键]
公共Guid Id{get;set;}
}

我找到了一个很好的解决方案,允许您指定应该匹配的属性。但是,它不需要单个实体,而是每个调用中的一个列表。它可能会给你一些提示,告诉你如何实现一个更好的版本,就像旧版本一样


(代码不是我的)

有一个扩展方法


使用Entity Framework Core(2.0)时,没有一个答案对我有效,因此以下是对我有效的解决方案:

public static class DbSetExtensions
{

    public static void AddOrUpdate<T>(this DbSet<T> dbSet, Expression<Func<T, object>> identifierExpression, params T[] entities) where T : class
    {
        foreach (var entity in entities)
            AddOrUpdate(dbSet, identifierExpression, entity);
    }


    public static void AddOrUpdate<T>(this DbSet<T> dbSet, Expression<Func<T, object>> identifierExpression, T entity) where T : class
    {
        if (identifierExpression == null)
            throw new ArgumentNullException(nameof(identifierExpression));
        if (entity == null)
            throw new ArgumentNullException(nameof(entity));

        var keyObject = identifierExpression.Compile()(entity);
        var parameter = Expression.Parameter(typeof(T), "p");

        var lambda = Expression.Lambda<Func<T, bool>>(
            Expression.Equal(
                ReplaceParameter(identifierExpression.Body, parameter),
                Expression.Constant(keyObject)),
            parameter);

        var item = dbSet.FirstOrDefault(lambda.Compile());
        if (item == null)
        {
            // easy case
            dbSet.Add(entity);
        }
        else
        {
            // get Key fields, using KeyAttribute if possible otherwise convention
            var dataType = typeof(T);
            var keyFields = dataType.GetProperties().Where(p => p.GetCustomAttribute<KeyAttribute>() != null).ToList();
            if (!keyFields.Any())
            {
                string idName = dataType.Name + "Id";
                keyFields = dataType.GetProperties().Where(p => 
                    string.Equals(p.Name, "Id", StringComparison.OrdinalIgnoreCase) || 
                    string.Equals(p.Name, idName, StringComparison.OrdinalIgnoreCase)).ToList();
            }

            // update all non key and non collection properties
            foreach (var p in typeof(T).GetProperties().Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null))
            {
                // ignore collections
                if (p.PropertyType != typeof(string) && p.PropertyType.GetInterface(nameof(System.Collections.IEnumerable)) != null)
                    continue;

                // ignore ID fields
                if (keyFields.Any(x => x.Name == p.Name))
                    continue;

                var existingValue = p.GetValue(entity);
                if (!Equals(p.GetValue(item), existingValue))
                {
                    p.SetValue(item, existingValue);
                }
            }

            // also update key values on incoming data item where appropriate
            foreach (var idField in keyFields.Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null))
            {
                var existingValue = idField.GetValue(item);
                if (!Equals(idField.GetValue(entity), existingValue))
                {
                    idField.SetValue(entity, existingValue);
                }
            }
        }
    }


    private static Expression ReplaceParameter(Expression oldExpression, ParameterExpression newParameter)
    {
        switch (oldExpression.NodeType)
        {
            case ExpressionType.MemberAccess:
                var m = (MemberExpression)oldExpression;
                return Expression.MakeMemberAccess(newParameter, m.Member);
            case ExpressionType.New:
                var newExpression = (NewExpression)oldExpression;
                var arguments = new List<Expression>();
                foreach (var a in newExpression.Arguments)
                    arguments.Add(ReplaceParameter(a, newParameter));
                var returnValue = Expression.New(newExpression.Constructor, arguments.ToArray());
                return returnValue;
            default:
                throw new NotSupportedException("Unknown expression type for AddOrUpdate: " + oldExpression.NodeType);
        }
    }
}
然后context.SaveChanges()将数据提交到数据库

我从的答案开始,修改了两件事:

  • 我使用fluent api进行密钥指定,因此我寻找实体的主键,而不是实体上的属性
  • 我有零钱
    public static class DbSetExtensions
    {
    
        public static void AddOrUpdate<T>(this DbSet<T> dbSet, Expression<Func<T, object>> identifierExpression, params T[] entities) where T : class
        {
            foreach (var entity in entities)
                AddOrUpdate(dbSet, identifierExpression, entity);
        }
    
    
        public static void AddOrUpdate<T>(this DbSet<T> dbSet, Expression<Func<T, object>> identifierExpression, T entity) where T : class
        {
            if (identifierExpression == null)
                throw new ArgumentNullException(nameof(identifierExpression));
            if (entity == null)
                throw new ArgumentNullException(nameof(entity));
    
            var keyObject = identifierExpression.Compile()(entity);
            var parameter = Expression.Parameter(typeof(T), "p");
    
            var lambda = Expression.Lambda<Func<T, bool>>(
                Expression.Equal(
                    ReplaceParameter(identifierExpression.Body, parameter),
                    Expression.Constant(keyObject)),
                parameter);
    
            var item = dbSet.FirstOrDefault(lambda.Compile());
            if (item == null)
            {
                // easy case
                dbSet.Add(entity);
            }
            else
            {
                // get Key fields, using KeyAttribute if possible otherwise convention
                var dataType = typeof(T);
                var keyFields = dataType.GetProperties().Where(p => p.GetCustomAttribute<KeyAttribute>() != null).ToList();
                if (!keyFields.Any())
                {
                    string idName = dataType.Name + "Id";
                    keyFields = dataType.GetProperties().Where(p => 
                        string.Equals(p.Name, "Id", StringComparison.OrdinalIgnoreCase) || 
                        string.Equals(p.Name, idName, StringComparison.OrdinalIgnoreCase)).ToList();
                }
    
                // update all non key and non collection properties
                foreach (var p in typeof(T).GetProperties().Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null))
                {
                    // ignore collections
                    if (p.PropertyType != typeof(string) && p.PropertyType.GetInterface(nameof(System.Collections.IEnumerable)) != null)
                        continue;
    
                    // ignore ID fields
                    if (keyFields.Any(x => x.Name == p.Name))
                        continue;
    
                    var existingValue = p.GetValue(entity);
                    if (!Equals(p.GetValue(item), existingValue))
                    {
                        p.SetValue(item, existingValue);
                    }
                }
    
                // also update key values on incoming data item where appropriate
                foreach (var idField in keyFields.Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null))
                {
                    var existingValue = idField.GetValue(item);
                    if (!Equals(idField.GetValue(entity), existingValue))
                    {
                        idField.SetValue(entity, existingValue);
                    }
                }
            }
        }
    
    
        private static Expression ReplaceParameter(Expression oldExpression, ParameterExpression newParameter)
        {
            switch (oldExpression.NodeType)
            {
                case ExpressionType.MemberAccess:
                    var m = (MemberExpression)oldExpression;
                    return Expression.MakeMemberAccess(newParameter, m.Member);
                case ExpressionType.New:
                    var newExpression = (NewExpression)oldExpression;
                    var arguments = new List<Expression>();
                    foreach (var a in newExpression.Arguments)
                        arguments.Add(ReplaceParameter(a, newParameter));
                    var returnValue = Expression.New(newExpression.Constructor, arguments.ToArray());
                    return returnValue;
                default:
                    throw new NotSupportedException("Unknown expression type for AddOrUpdate: " + oldExpression.NodeType);
            }
        }
    }
    
    context.Projects.AddOrUpdate(x => x.Name, new Project { ... })
    context.Projects.AddOrUpdate(x => new { x.Name, x.Description }, new Project { ... })
    
    public TEntity AddOrUpdate(TEntity entity)
    {
        var entityEntry = Context.Entry(entity);
    
        var primaryKeyName = entityEntry.Context.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties
            .Select(x => x.Name).Single();
    
        var primaryKeyField = entity.GetType().GetProperty(primaryKeyName);
    
        var t = typeof(TEntity);
        if (primaryKeyField == null)
        {
            throw new Exception($"{t.FullName} does not have a primary key specified. Unable to exec AddOrUpdate call.");
        }
        var keyVal = primaryKeyField.GetValue(entity);
        var dbVal = DbSet.Find(keyVal);
    
        if (dbVal != null)
        {
            Context.Entry(dbVal).CurrentValues.SetValues(entity);
            DbSet.Update(dbVal);
    
            entity = dbVal;
        }
        else
        {
            DbSet.Add(entity);
        }
    
        return entity;
    }
    
    public static TEntity AddOrUpdate<TEntity>(this DbSet<TEntity> dbSet, DbContext context, Func<TEntity, object> identifier, TEntity entity) where TEntity : class
    {
        TEntity result = dbSet.Find(identifier.Invoke(entity));
        if (result != null)
        {
            context.Entry(result).CurrentValues.SetValues(entity);
            dbSet.Update(result);
            return result;
        }
        else
        {
            dbSet.Add(entity);
            return entity;
        }
    }
    
    dbContext.MyModels.AddOrUpdate(dbContext, model => model.Id, new MyModel() { Id = 3 });