What happened to AddOrUpdate in EF 7 / Core?

后端 未结 11 1257
暖寄归人
暖寄归人 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 09:48

    I've found a nice solution that allows you specify the property that should match. However, it doesn't take a single entity, but a list in each call. It may give you some hints on how to implement a better version that works like the good-olde one.

    https://github.com/aspnet/MusicStore/blob/7787e963dd0b7293ff95b28dcae92407231e0300/samples/MusicStore/Models/SampleData.cs#L48

    (Code isn't mine)

    0 讨论(0)
  • 2020-12-16 09:51

    I started with Tjaart's answer and modified two things:

    1. I'm using the fluent api for key designation so I'm looking for the entity's primary key instead of an attribute on the entity
    2. I have change tracking turned on and was getting the error others have mentioned regarding that EF is already tracking it. This does a find on the already tracked entity and copies the values from the incoming entity to it, then updates the original entity

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

    I've been able to get decent mileage out of it so far without any problems.

    I'm using this on EFCore 2.1

    0 讨论(0)
  • 2020-12-16 09:51

    You can use this extension method I created to patch our codebase for the migration to 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);
            }
    
    0 讨论(0)
  • 2020-12-16 09:55

    This solution is, I think, a simpler solution to this problem, if assuming a base entity class is a legit option. The simplicity comes from your domain entities implementing DomainEntityBase, which alleviates a lot of the complexities in the other suggested solutions.

    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; }
    }
    
    0 讨论(0)
  • 2020-12-16 10:01

    It's waiting to be implemented. See issues #629 & #4526.

    Update: according to comments below (unverified) - this feature is finally slated for release in .NET Core 2.1!

    0 讨论(0)
  • 2020-12-16 10:03

    I don't understand why people are trying to find the primary key in the other answers. Just pass it when you're calling the method as it's done in EF 6 AddOrUpdate method.

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

    and use it later like this:

    dbContext.MyModels.AddOrUpdate(dbContext, model => m.Id, new MyModel() { Id = 3 });
    

    Clean and performant.

    0 讨论(0)
提交回复
热议问题