0

In EF Core 3.1+, I would like to seed all DBSets from ANY DbContext with data programmatically with a Seed() extension method. The Seed() method works beautifully in a non-dynamic situation.

For the manual solution, I can add code (that works adding records for each entity) like:

   Categories.AddRange(Categories.ToList().Seed(10));
   Products.AddRange(Products.ToList().Seed(20));
   ...
   SaveChanges();

My approach for the dynamic solution that would work for any DbContext is code like this:

var entityTypes = myContext.Model.GetEntityTypes();

foreach (var et in entityTypes)
{
    myContext.Set(et.ClrType).ToList().Seed();
}

That is referencing a extension method:

public static class Queryables
{
    public static IQueryable<object> Set(this DbContext _context, Type t)
    {
        return (IQueryable<object>)_context.GetType()
            .GetMethod("Set")
            .MakeGenericMethod(t)
            .Invoke(_context, null);
    }
}

but returns an error:

System.Reflection.AmbiguousMatchException: 'Ambiguous match found.'

This is for a generic solution using EF 3.1+ where I want to loop through and seed whatever DbSets are in the current context.

I can manually add code (that works adding records for each entity) like:

   Categories.AddRange(Categories.ToList().Seed(10));
   Products.AddRange(Products.ToList().Seed(20));
   ...
   SaveChanges();

Any idea how to get generic programmatic access to the DBSets so that I can add data to each one?

=== Edit ==========================================================

I updated the extension method as I realized that the duplicate was for multiple methods returned, so I got that part working as below. This code returns the DBSet correctly (sort of):

public static IQueryable<object> Set(this DbContext _context, Type T)
{
    return (IQueryable<object>)_context.GetType()
            .GetMethods()
            .First(p => p.Name == "Set" && p.ContainsGenericParameters)
            .MakeGenericMethod(T)
            .Invoke(_context, null);
}

Now the code runs without throwing an error, but the item is not added.

Simplifying the code so that the Seed() function is not a factor, I decided to test by just adding a single record as below. I can see the DBSet but still no new record.

// Does no throw an error but does not add the item
dynamic newItem = Activator.CreateInstance(et.ClrType);
var set = myContext.Set(et.ClrType);
set.ToList().Add(newItem); // Simplified from .Seed(); 

Any ideas on this?

WillC
  • 1,761
  • 17
  • 37

2 Answers2

1

DbContext has multiple Set<T> methods with different parameters.

The easy way to get the right MethodInfo is to create a matching delegate;

var method = new Func<DbSet<object>>(_context.Set<object>).Method.GetGenericMethodDefinition();
method.MakeGenericMethod(T).Invoke( ... );

Of course, if you have defined DbSet properties for each type, you could loop through those.

Your other problem, is set.ToList().Add(.... Calling ToList loads the entire table into a list object, that doesn't know anything about the DbSet it came from. You need to call DbSet<T>.Add, not List<T>.Add.

I would move your entire seed method into a generic method, make that generic for each type, then call it.

private void Seed<T>(DbContext context, T obj) =>
    context.Set<T>.Add(obj);

var method = new Action<DbContext, object>(Seed<object>).Method.GetGenericMethodDefinition();
foreach (var et in entityTypes)
    method.MakeGenericMethod(et.ClrType).Invoke( ... );
Jeremy Lakeman
  • 9,515
  • 25
  • 29
1

It seemed that this would be a simple thing to do for EF Core but it has not turned out to be so and may not be possible.

From this link, it seems that recent changes to EF have made this harder to do.

If you jump through hoops with reflection using 'DbContent.Set' to get a list of DbSets, you will find these to actually be of 'InternalDbSet' type with a bunch of warnings about how you should not use them. There is no way to cast them to DbSet or an interface such as IDbSet.

But if anyone has a good solution, please post, as I now intrigued to see whether it can be done.

WillC
  • 1,761
  • 17
  • 37