Использование единого Entity Framework Core DbContext для управления несколькими схемами базы данных с одноименными таблицами - PullRequest
0 голосов
/ 28 августа 2018

В библиотеке .NET Core 2.1 мне нужен доступ к базе данных MySQL, организованной в несколько схем с таблицами , которые могут иметь одинаковые имена в этих схемах . Я не могу вносить какие-либо изменения в БД, поскольку она принадлежит другой компании. Для большинства таблиц мне нужен доступ только для чтения, и я хотел бы использовать одно ядро ​​EF DbContext.

На самом деле я получаю это сообщение об ошибке во время инициализации:

InvalidOperationException: невозможно использовать таблицу 'tbl_panel' для тип сущности 'Db2Panels', поскольку он используется для сущности введите 'Db1Panels', и между их первичные ключи.

Я думаю, что суть вопроса в основном заключается в методах конфигурации, которые следует вызывать не один раз, а N раз, по одному для каждого экземпляра сущности с другой схемой (db_machine_1.tbl_panel, db_machine_2.tbl_panel и т. Д.). ). Как я могу достичь своей цели?

Это моя фактическая реализация.

Схемы базы данных

// 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

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();
    }
}

Предметы

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

Конфигурация

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();
    }
}

ОБНОВЛЕНИЕ массивов базового класса

После создания базовых абстрактных классов и производных я хотел бы объединить все объекты производного класса в один массив, чтобы иметь возможность доступа к конкретному DbSet с помощью номера схемы. Смотрите также приведенный выше код конструктора DbContext. У меня проблемы с кастингом ...

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();

Возможно ли это как-нибудь?

Ответы [ 2 ]

0 голосов
/ 28 августа 2018

Вы должны иметь возможность использовать атрибут Table. Там есть параметр Schema, который позволяет вам установить имя схемы. См. здесь для документации. В вашем случае вы получите что-то вроде

[Table("Table1", Schema="Schema1")]
public class Entity1Schema1
{
    public string Property1 {get;set;}
}

[Table("Table1", Schema="Schema2")]
public class Entity1Schema2
{
    public string Property1 {get;set;}
}

И затем, конечно, вы можете использовать интерфейсы или базовые классы для рефакторинга вашего кода, как уже упоминалось @ ste-fu.

0 голосов
/ 28 августа 2018

Я думаю, что вы не можете избежать двух разных объектов EF для разных таблиц, и вам, вероятно, не следует этого делать, так как они могут расходиться в какой-то момент в будущем.

Как минимум вам нужно два класса Db1Panel и Db2Panel. Я предполагаю, что на самом деле префикс "Db" означает другую схему, а не другую базу данных.

Однако это не должно быть большой проблемой, так как в C # есть другие способы заставить их вести себя подобным образом. На ум приходят два варианта: наследовать их от одного базового класса или реализовать интерфейс:

public abstract class PanelBase
{
    public long Id { get; set; }
    // other properties
}

[Table("tbl_panel", Schema = "Db1")]
public class Db1Panel : PanelBase{}

[Table("tbl_panel", Schema = "Db2")]
public class Db2Panel : PanelBase{}

Если вы решили реализовать интерфейс, вам нужно будет повторить свойства в каждом классе, но инструменты рефакторинга делают это довольно просто.

public interface IPanel
{
    public long Id { get; set; }
}

[Table("tbl_panel", Schema = "Db1")]
public class Db1Panel : IPanel
{
    public long Id { get; set; }
}

[Table("tbl_panel", Schema = "Db2")]
public class Db2Panel : IPanel
{
    public long Id { get; set; }
}

Или, в зависимости от размера вашего приложения, вы можете рассмотреть возможность использования другого пространства имен объектов домена и просто отобразить в нем объекты базы данных:

...