1
votes

When using EntityFramework with the Code First approach, you usually derive from DbContext and create DbSet properties for all entity types you have.

I can't do this because my software can be used with modules that provide new entity types that are not known at compile time.

So I would like to determine the list of available entity types at runtime.

I got it partially working with this code:

public class MyDbContext: DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");

        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            // Register all types that derive from ModelBase as entity types
            var entityTypes = assembly
                .GetTypes()
                .Where(t => t.IsSubclassOf(typeof(ModelBase)));

            foreach (var type in entityTypes)
            {
                entityMethod.MakeGenericMethod(type)
                    .Invoke(modelBuilder, new object[] { });
            }
        }
    }
}

However, when I do this, OnModelCreating is never called. I have to add a DbSet to my class. I can not even make the DbSet private or internal, it has to be public, which makes it an ugly workaround:

public DbSet<DummyType> DummyTypes { get; set; }

public MyDbContext(string connectionName):
    base(connectionName)
{
    // Force initialization to make sure OnModelCreating
    // has been called after leaving the constructor
    DummyTypes.Count();
}

When adding this workaround, I can access the DbSets by calling the DbContext.Set<EntityType>() method, so everything works as intended.

Question: Is there a way to achieve the same thing without adding a public DbSet property to my class?

1
Your type is dynamic, so how can you use this DbContext.Set<EntityType>()? configuring using code can be complex, your reflection code is just simple, what about the mappings? isn't that mappings is also dynamic? Your model seems empty at compile time. So I think you can try creating your DbContext using constructor accepting a DbCompiledModel. DbCompiledModel can be obtained from Compile method of DbModel which can be obtained from Build method of DbModelBuilder. That means you start from a DbModelBuilder as an instance created any where you like. - Hopeless
The types are not created at runtime, they are just not known when compiling the relevant assembly. This is for a library that can be used with other assemblies which can provide new entity types. As for the mappings, I have not yet thought about that. But thanks, I will have a look into the DbModelBuilder class. - Lukas Boersma

1 Answers

1
votes

Can't you solve your problem in a more static way? For instance, each library can also provide it own DbContext implementation. If libraries are statically unaware of each other, then there is no static way for them to have navigational properties to classes in other libraries. Then you can have independent DbContexts, one for each library. How to use them in your main code? It depends on your implementation, but I guess you can figure it out.

When libraries reference each other, there can be no loops, so, they form logical topological ordering, For instance if library A And B refer to C, you can have DbContexts in A and B and not in C, if it does not use its own entities.

Traditionally, there was nothing that could prevent you from doing this in EF, unless you needed migrations. Now EF supports migrations for different DbContexts on the same database.