In a .NET Core 2.1 library I need to access to a MySQL
database organized in multiple schemas with tables that can have the same name across those schemas. I can't make any changes to the DB since it comes from another company.
For most of the tables I need a read-only access and I'd like to use a single EF Core DbContext
.
Actually I get this error message during initialization:
InvalidOperationException: Cannot use table 'tbl_panel' for entity type 'Db2Panels' since it is being used for entity type 'Db1Panels' and there is no relationship between their primary keys.
I think that the crux of the matter mainly resides in the configuration methods, which should be called not just once but N times, one for each instance of the entity with different schema (db_machine_1.tbl_panel
, db_machine_2.tbl_panel
, etc.).
How can I reach my goal?
This is my actual implementation.
Database schemas
// db_machine_1 schema
db_machine_1.tbl_panel
db_machine_1.tbl_basket
db_machine_1.tbl_unit
// db_machine_2 schema
db_machine_2.tbl_panel
db_machine_2.tbl_basket
db_machine_2.tbl_discard
// Other db_machine_X schemas with similar structure...
DbContext configuration
public class MyDbContext : DbContext
{
// Schema: db_machine_1
public DbSet<Panel> Db1Panels { get; set; }
public DbSet<Basket> Db1Baskets { get; set; }
public DbSet<Unit> Db1Units { get; set; }
// Schema: db_machine_2
public DbSet<Panel> Db2Panels { get; set; }
public DbSet<Basket> Db2Baskets { get; set; }
public DbSet<Discard> Db2Discards { get; set; }
// Other schemas DbSet<X> objects...
// Arrays to access the specific DbSet by using the schema number:
// Panels[1] -> Db1Panels, Panels[2] -> Db2Panels, ...
public DbSet<Panel>[] Panels { get; }
public DbSet<Basket>[] Baskets { get; }
// Other arrays for other DbSet<X> objects...
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
// Arrays initialization
List<DbSet<Panel>> dbPanelList = new List<DbSet<Panel>>();
dbPanelList.Add(Db1Panels);
dbPanelList.Add(Db2Panels);
Panels = dbPanelList.ToArray();
List<DbSet<Basket>> dbBasketList = new List<DbSet<Basket>>();
dbBasketList.Add(Db1Baskets);
dbBasketList.Add(Db2Baskets);
Baskets = dbBasketList.ToArray();
// Initialization for other DbSet<X> objects...
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyAllConfigurations<MyDbContext>();
modelBuilder.ApplyAllConversions();
}
}
Objects
public class Panel
{
public long Id { get; set; }
public string SN { get; set; }
// Other properties...
}
public class Basket
{
public long Id { get; set; }
public string Description { get; set; }
// Other properties...
}
Configurations
public class PanelConfiguration : IEntityTypeConfiguration<Panel>
{
public void Configure(EntityTypeBuilder<Panel> builder)
{
builder.ToTable("tbl_panel");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("ID_Record");
builder.Property(e => e.SN)
.HasColumnName("Serial")
.HasMaxLength(20);
// Other properties configuration...
}
}
public class BasketConfiguration : IEntityTypeConfiguration<Basket>
{
public void Configure(EntityTypeBuilder<Basket> builder)
{
builder.ToTable("tbl_basket");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("ID_Record");
builder.Property(e => e.Description)
.HasColumnName("Desc")
.HasMaxLength(100);
// Other properties configuration...
}
}
// Other IEntityTypeConfiguration implementations for other tables...
// This extension method is used to automatically load all Configurations
// of the various entities
public static class ModelBuilderExtensions
{
public static void ApplyAllConfigurations(this ModelBuilder modelBuilder)
{
var applyConfigurationMethodInfo = modelBuilder
.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.First(m => m.Name.Equals("ApplyConfiguration", StringComparison.OrdinalIgnoreCase));
var ret = typeof(T).Assembly
.GetTypes()
.Select(t => (t, i: t.GetInterfaces().FirstOrDefault(i => i.Name.Equals(typeof(IEntityTypeConfiguration<>).Name, StringComparison.Ordinal))))
.Where(it => it.i != null)
.Select(it => (et: it.i.GetGenericArguments()[0], cfgObj: Activator.CreateInstance(it.t)))
.Select(it => applyConfigurationMethodInfo.MakeGenericMethod(it.et).Invoke(modelBuilder, new[] { it.cfgObj }))
.ToList();
}
}
UPDATE about base class arrays
After creating base abstract classes and derived ones, I'd like to merge all the derived class objects into a single array to be able to access the specific DbSet
by using the schema number. See also above code of DbContext
constructor.
I'm having problems with casting...
List<DbSet<Panel>> dbPanelList = new List<DbSet<Panel>>();
dbPanelList.Add((DbSet<Panel>)Db1Panels.Select(g => g as Panel)); // NOT WORKING! Cast Exception
dbPanelList.Add((DbSet<Panel>)Db2Panels.Cast<DbSet<Panel>>()); // NOT WORKING! Cast Exception
Panels = dbPanelList.ToArray();
Is this possible somehow?