Entity Framework Core: What is the fastest way to check if a generic entity is already in the dbset and then add or update it?

c# entity-framework entity-framework-core

Question

I load data from a CSV file, I create entities on the fly then I add them to a List<T>.

The following code takes a List<T> and adds the entities to the right DbSet.

public static void AddEntities<T>(List<T> entities, DbContext db) where T :class
{
    using (db)
    {
        var set = db.Set<T>();

        foreach(T e in entities)
        {
            set.Add(e);
        }
        db.SaveChanges();
     }
}

I'd like to change it to add the entity only if it doesn't exist, otherwise it has to update it.

What is the best way to accomplish this with Entity Framework Core? I believe I should use System.Reflection to:

  • get at least the ID but it would be better to get all the keys for the dbset
  • loop through the keys to find if an entity is already in the DbSet
  • if found then update it with the values from the new entity otherwise add it to the set

Something like this:

public static void AddEntities<T>(List<T> entities, DbContext db) where T :class
{
    using (db)
    {
        var set = db.Set<T>();

        foreach(T e in entities)
        {
            var idProperty = e.GetType().GetProperty("ID").GetValue(e,null);
            var obj = set.Find(idProperty);

            if (obj==null)
            {
                set.Add(e);
            }
            else
            {
                var properties = (typeof(T)).GetProperties();

                foreach (var p in properties)
                {
                    var value = e.GetType().GetProperty(p.Name).GetValue(e,null);
                    obj.GetType().GetProperty(p.Name).SetValue(obj,value);
                 }
            }         

        }
        db.SaveChanges();
    }
}

The code runs from 3 to 4 times slower than the simple add.

Is there a faster way? All the code examples I am finding are all for EF6, based on ObjectContext and IObjectContextAdapter and it seems this kind of code do not longer work with EF Core.

1
2
2/28/2017 7:57:28 AM

Accepted Answer

Instead of reflection, you could use the EF Core public (and some internal) metadata services to get the key values needed for Find method. For setting the modified values you could use EntityEntry.CurrentValues.SetValues method.

Something like this:

using Microsoft.EntityFrameworkCore.Metadata.Internal;

public static void AddEntities<T>(List<T> entities, DbContext db) where T : class
{
    using (db)
    {
        var set = db.Set<T>();

        var entityType = db.Model.FindEntityType(typeof(T));
        var primaryKey = entityType.FindPrimaryKey();
        var keyValues = new object[primaryKey.Properties.Count];

        foreach (T e in entities)
        {
            for (int i = 0; i < keyValues.Length; i++)
                keyValues[i] = primaryKey.Properties[i].GetGetter().GetClrValue(e);

            var obj = set.Find(keyValues);

            if (obj == null)
            {
                set.Add(e);
            }
            else
            {
                db.Entry(obj).CurrentValues.SetValues(e);
            }
        }
        db.SaveChanges();
    }
}
6
2/27/2017 2:49:54 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow