What happened to AddOrUpdate in EF 7 / Core?

后端 未结 11 1288
暖寄归人
暖寄归人 2020-12-16 09:30

I\'m writing a seed method using EntityFramework.Core 7.0.0-rc1-final.

What happened to the AddOrUpdate method of DbSet?

11条回答
  •  余生分开走
    2020-12-16 10:10

    Here is my solution based on other solutions from this thread.

    • Supports composite keys
    • Supports shadow-property keys.
    • Stays within EF Core realm and doesn't use reflection.
    • Change _appDb to your context.
    
            public object PrimaryKeyValues(TEntity entity)
            {
                var properties = _appDb.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties;
    
                var entry = _appDb.Entry(entity);
    
                var values = properties?.Select(p => entry.Property(p.Name).CurrentValue);
    
                if (values?.Count() == 1)
                    return values.Single();
    
                return values?.ToArray();
            }
    
    
            public async Task AddOrUpdateAsync(TEntity entity) where TEntity : class
            {
                var pkValue = PrimaryKeyValues(entity);
    
                if (pkValue == null)
                {
                    throw new Exception($"{typeof(TEntity).FullName} does not have a primary key specified. Unable to exec AddOrUpdateAsync call.");
                }
    
                if ((await _appDb.FindAsync(typeof(TEntity), pkValue)) is TEntity dbEntry)
                {
                    _appDb.Entry(dbEntry).CurrentValues.SetValues(entity);
                    _appDb.Update(dbEntry);
    
                    entity = dbEntry;
                }
                else
                {
                    _appDb.Add(entity);
                }
    
                return entity;
            }
    

    Update - Add Or Update Range

    Complete solution. Doesn't support keys that are shadow properties

    DbContextExtensions.cs

            // FIND ALL
            // ===============================================================
            /// 
            /// Tries to get all entities by their primary keys. Return all/partial/empty array of database entities.
            /// 
            /// 
            /// 
            /// 
            /// 
            public static async Task FindAllAsync(this DbContext dbContext, IEnumerable args) where TEntity : class
            {
                return await Task.Run(() => { 
                    var dbParameter = Expression.Parameter(typeof(TEntity), typeof(TEntity).Name);
    
                    var properties = dbContext.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey()?.Properties;
    
                    if (properties == null)
                        throw new ArgumentException($"{typeof(TEntity).FullName} does not have a primary key specified.");
    
                    if (args == null)
                        throw new ArgumentNullException($"Entities to find argument cannot be null");
    
                    if (!args.Any())
                        return Enumerable.Empty().ToArray();
    
                    var aggregatedExpression = args.Select(entity =>
                    {
                        var entry = dbContext.Entry(entity);
    
                        return properties.Select(p =>
                        {
                            var dbProp = dbParameter.Type.GetProperty(p.Name); 
                            var left = Expression.Property(dbParameter, dbProp); 
    
                            var argValue = entry.Property(p.Name).CurrentValue;
                            var right = Expression.Constant(argValue);
    
                            return Expression.Equal(left, right);
                        })
                        .Aggregate((acc, next) => Expression.And(acc, next));
                    })
                    .Aggregate((acc, next) => Expression.OrElse(acc, next));
    
                    var whereMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Where" && m.GetParameters().Length == 2);
                    MethodInfo genericWhereMethod = whereMethod.MakeGenericMethod(typeof(TEntity));
    
                    var whereLambda = Expression.Lambda(aggregatedExpression, dbParameter);
    
                    var set = dbContext.Set();
                    var func = whereLambda.Compile();
    
                    var result = genericWhereMethod.Invoke(null, new object[] { set, func}) as IEnumerable;
    
                    return result.ToArray();
                });
            }
    
            // ADD OR UPDATE - RANGE - ASYNC
            // ===============================================================
            /// 
            /// Foreach entity in a range, adds it when it doesn't exist otherwise updates it. Bases decision on Pk.
            /// 
            /// 
            /// 
            /// 
            /// 
            public static async Task<(int AddedCount, int UpdatedCount)> AddOrUpdateRangeAsync(this DbContext dbContext, IEnumerable entities) where TEntity : class
            {
                var existingEntitiesHashes = (await dbContext.FindAllAsync(entities)).Select(x =>
                {
                    dbContext.Entry(x).State = EntityState.Detached;
                    return dbContext.PrimaryKeyHash(x);
                });
    
                var (True, False) = entities.DivideOn(x => existingEntitiesHashes.Contains(dbContext.PrimaryKeyHash(x)));
    
                dbContext.UpdateRange(True);
                dbContext.AddRange(False);
    
                return (AddedCount: False.Count(), UpdatedCount: True.Count());
            }
    
    
            // ADD OR UPDATE - ASYNC
            // ===============================================================
            /// 
            /// Adds when not existing otherwise updates an entity. Bases decision on Pk.
            /// 
            /// 
            /// 
            /// 
            /// 
            public static async Task AddOrUpdateAsync(this DbContext dbContext, TEntity entity) where TEntity : class
                => await dbContext.AddOrUpdateRangeAsync(new TEntity[] { entity });
    
            // PK HASH
            // ===============================================================
            /// 
            /// Returns the compounded hash string of all primary keys of the entity
            /// 
            /// 
            /// 
            /// 
            /// 
            public static string PrimaryKeyHash(this DbContext dbContext, TTarget entity)
            {
                var properties = dbContext.Model.FindEntityType(typeof(TTarget)).FindPrimaryKey().Properties;
    
                var entry = dbContext.Entry(entity);
    
                return properties.Select(p => Crypto.HashGUID(entry.Property(p.Name).CurrentValue))
                                 .Aggregate(string.Empty, (acc, next) => acc += next);
            }
    

    Crypto.cs

        public class Crypto
        {
            /// 
            /// RETURNS A HASH AS A GUID BASED ON OBJECT.TOSTRING()
            /// 
            /// 
            /// 
            public static string HashGUID(object obj)
            {
                string text = string.Empty;
                MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
                byte[] bytes = new UTF8Encoding().GetBytes(obj.ToString());
                byte[] array = md5CryptoServiceProvider.ComputeHash(bytes);
                for (int i = 0; i < array.Length; i++)
                {
                    text += Convert.ToString(array[i], 16).PadLeft(2, '0');
                }
                md5CryptoServiceProvider.Clear();
                return text.PadLeft(32, '0');
            }
        }
    

    IEnumerableExtensions.cs

            /// 
            /// Divides into two based on predicate
            /// 
            /// 
            /// 
            /// 
            /// 
            public static (IEnumerable True, IEnumerable False) DivideOn(this IEnumerable source, Func predicate)
                => (source.Where(x => predicate(x)), source.Where(x => !predicate(x)));
    

    Comment if use it (ノ◕ヮ◕)ノ✲゚。⋆

提交回复
热议问题