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.
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!