0

I have an application that uses a plugin style methodology (Plug-in architecture for ASP.NET MVC), to populate my EF code first database. During the PreApplicationStartMethod I am pulling a list of objects from each plugin (dll) to load into entity framework. I'm trying to use this generic approach (Generic method to insert record into DB) but it says that T is an object rather than the specific type that I have specified in the plugins. As a result the insert fails because I don't have a object dbset in my context. The same happens if I manually convert the objects to their original type.

IModule.cs (main project) public interface IModule { List> Entities { get; } }

Module.cs (plugin)

public class Module : IModule
{
    public List<List<object>> Entities
    {
        get
        {
            List<List<object>> entities = new List<List<object>>();

            List<object> renderings = new List<object>();
            renderings.Add(new FieldRendering(1, "Text", "String"));
            renderings.Add(new FieldRendering(2, "Date", "Date"));
            renderings.Add(new FieldRendering(3, "Hidden", "Auto"));

            entities.Add(renderings);

            return entities;
        }
    }
}

PreApplicationInit (main project)

[assembly: System.Web.PreApplicationStartMethod(typeof(Core.Modules.PreApplicationInit), "InitializeModules")]
public class PreApplicationInit
{
    private static Context context;

    public static void InitializeModules()
    {
        // code to pull in the modules (works, so suppressing for brevity)

        context = new Context();

        foreach (List<object> section in module.Entities)
        {
            foreach (object entity in section)
            {
                // Inspecting the entity object here indicates the correct type (FieldRendering)
                Add(entity);
            }

            context.SaveChanges();
        }
    }

    private static void Add<T>(T entity) where T : class
    {
        // Inspecting T here indicates the incorrect type (object)
        // Code fails here
        context.Set<T>().Add(entity);
    }
}
Community
  • 1
  • 1
neuhoffm
  • 33
  • 1
  • 7
  • Thanks guys, the challenge with @will and @aaronbregger approach is that the `context.Set(entity.GetType())` returns a DbSet rather than an IDbSet. The issue with this is when I try to check if the entity already exists I can't use `set.Contains(entity)` as that is specific to IDbSet and when I try to use reflection to get the property labeled with the key attribute I run into the same issue with reflection looking at type object rather than the specific type. I'll look at @TheEvilPenguin's approach tonight. – neuhoffm Sep 06 '13 at 14:40

3 Answers3

1

When inferring generic arguments the compiler uses the declared type of the variables passed, not what that variable contains at runtime.

If you want to do things this way, you could use reflection to call the method with the correct generic argument:

var addMethod = typeof(PreApplicationInit)
        .GetMethod("Add", BindingFlags.Static | BindingFlags.NonPublic)
        .MakeGenericMethod(o.GetType());
addMethod.Invoke(null, new[] { entity  });

If it's going to be called many times, I would create a Dictionary<Type, MethodInfo>() to store the MethodInfo objects in as you create them, as creation is slow.

You could also make the Add() method non-generic, and use reflection on context.Set<T>() within Add().

TheEvilPenguin
  • 5,634
  • 1
  • 26
  • 47
  • This works! final code: `Dictionary types = new Dictionary(); foreach (object entity in section) { if (!types.ContainsKey(entity.GetType())) { MethodInfo addMethod = typeof(PreApplicationInit) .GetMethod("Add",BindingFlags.Static | BindingFlags.NonPublic) .MakeGenericMethod(entity.GetType()); types.Add(entity.GetType(), addMethod); } types[entity.GetType()].Invoke(null, new[] { entity }); ` – neuhoffm Sep 07 '13 at 02:55
1

This code is working as it should do.

When looping through a list of objects, the Add(entity) call assigns 'object' to T as that is what it knows about the type.

Perhaps the best solution here is to have each of your entity modules derive from a given interface, that then allows you to cast it to that interface to register with the Context - I have not been able to test if this works though.

Does the Context have a Set(Type t) method?

If so you could easily just do:-

context.Set(entity.GetType()).Add(entity);
Will
  • 186
  • 9
0

Use the overloaded DbContext.Set method that accepts a type object instead of a generic type parameter:

foreach (object entity in section)
{
    context.Set(entity.GetType()).Add(entity);
}

The type used by a generic type parameter is determined at compile time. In this case, the compile time type of your entity is object. Instead of using a generic type parameter, you need to use the runtime type of your entity to get the DbSet you want to work with.

If you need to continuously add new entities to your set, you could catch the exception thrown when your context saves changes for each entity.

foreach (object entity in section)
{
    context.Set(entity.GetType()).Add(entity);

    try
    {
        context.SaveChanges();
    }
    catch (Exception e)
    {
        //do nothing
    }
}