Is that possible to have a single DbContext, with migration files being split in different assemblies?

天涯浪子 提交于 2020-01-02 07:00:10

问题


I've built a system which have a Core solution and a Custom solution for each of our clients. The Core has it own dbcontext, and each Customs have their own dbcontext for client specific tables. Custom solution is referencing Core solution dlls. Custom dlls are being dynamically loaded by the Core solution in a plugin architecture. Since Core and Custom have their own dbcontext, each one have their own migration files.

All is working pretty well except for one thing, i cannot make a linq query that join a Core table with Custom table. (Ex: joining ProjectCustom with Project in a single query). I get the error:

The specified LINQ expression contains references to queries that are associated with different contexts.

So to allow query joining tables from both Core and Custom, i'm thinking of going another route where the Core define a base abstract DbContext and the Custom inherits from this dbcontext and add its custom tables. So i have a single dbcontext at run time.

Now to my question, is there any way that i could have migration files in Core, and another set of migration files defined in Custom ?

I've made a working solution where all migrations were maintained in the Custom solution. However i don't like the fact that if i make a manual migration affecting Core tables, i have to manually propagate it to all Custom solutions.

I would like to keep migrations affecting Core tables into that Core solution, and have another set of migrations for customs. All applied to the same dbcontext, which the final implementation reside in the custom solution.

Is that possible ? Is there any way to configure some kind of provider for the migrations files ? So i could first apply migrations defined in Core solution then apply migration from custom, using the same dbcontext.


回答1:


Source Code

Let's start by creating an interface for the Context's configuration

public interface IDbContextRegistry
{
    void Configure(DbModelBuilder builder);
}

In my case, the implementation lives in the each individual Context but implementing this in a diff class will be nice.

public class ContextA : DbContext, IDbContextRegistry
{
    static ContextA()
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<ContextA, ConfigurationA>());
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        Configure(modelBuilder);
    }

    public void Configure(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ModelA>();
    }
}
internal sealed class ConfigurationA : DbMigrationsConfiguration<ContextA>
{
    public ConfigurationA()
    {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(ContextA context)
    {
    }
}

public class ModelA
{
    public int Id { get; set; }
    public string Name { get; set; }
}

And

public class ContextB : DbContext, IDbContextRegistry
{
    static ContextB()
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<ContextB, ConfigurationB>());
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        Configure(modelBuilder);
    }

    public void Configure(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ModelB>();
    }
}
internal sealed class ConfigurationB : DbMigrationsConfiguration<ContextB>
{
    public ConfigurationB()
    {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(ContextB context)
    {
    }
}

public class ModelB
{
    public int Id { get; set; }
    public DateTimeOffset Date { get; set; }
}

Later in your main app create a Context that will register all the individual types.

public class ContextJoin : DbContext
{
    private readonly List<IDbContextRegistry> _registry = new List<IDbContextRegistry>();

    // Constructor to allow automatic DI injection for most modern containers
    public ContextJoin(IEnumerable<IDbContextRegistry> registry)
    {
        _registry.AddRange(registry);
    }

    public ContextJoin(params IDbContextRegistry[] registry) : this(_registry.AsEnumerable())
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        foreach (var entry in _registry)
        {
            entry.Configure(modelBuilder);
        }
    }
}

and voila, every entity is living in the ContextJoin and ready to use. Example:

class Program
{
    static void Main(string[] args)
    {
        ContextA ctxA = new ContextA();
        ctxA.Set<ModelA>().Add(new ModelA());
        ctxA.SaveChanges();

        ContextB ctxB = new ContextB();
        ctxB.Set<ModelB>().Add(new ModelB());
        ctxB.SaveChanges();

        ContextJoin ctxJoin = new ContextJoin(ctxA, ctxB);
        ctxJoin.Set<ModelB>().Add(new ModelB());
        ctxJoin.Set<ModelA>().Add(new ModelA());
        ctxJoin.SaveChanges();

        var crossQuery = ctxJoin.Set<ModelA>().Join(
            ctxJoin.Set<ModelB>(), t => t.Id, t => t.Id, (a, b) => new
            {
                a.Name,
                b.Date
            }).ToList();
        crossQuery.ForEach(t => Console.WriteLine($"Name: {t.Name}, Date: {t.Date}"));
    }
}

Hope this help!



来源:https://stackoverflow.com/questions/43396067/is-that-possible-to-have-a-single-dbcontext-with-migration-files-being-split-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!