使用实体框架(或其他ORM?)管理几个几乎相同的客户端数据库

本文关键字:几个 数据库 客户端 其他 实体 ORM 管理 框架 | 更新日期: 2023-09-27 18:08:31

我正在制作一个ASP原型。. NET Web API,需要与几个几乎相同的数据库进行通信。我们的每个客户都有自己的数据库结构实例,但有些客户专门与他们拥有的其他系统集成。例如,在一个数据库中,Client表可能有AbcID列来引用另一个系统中的表,但其他数据库不会有这个列。除此之外,这两个表在名称和列上是相同的。列也可以有不同的长度,例如varchar(50)而不是varchar(40)。在一些数据库中,可能会有一个额外的表。我先把重点放在解决不同列的问题上。

我希望使用ORM来处理API的数据访问层,现在我正在尝试使用实体框架。我已经解决了如何从api调用动态连接到不同的数据库,但现在它们必须在结构上完全相同。

我尝试用数据库优先的方法设置双。edmx模型,但这会导致模型之间的类名冲突。因此,我尝试了代码优先,并提出了这个(这不起作用)。

DbContext扩展:在构造函数中,我检查正在访问哪个数据库,如果它是一个特殊的数据库,我将其标记为模型配置。

public partial class MK_DatabaseEntities : DbContext
{
    private string _dbType = "dbTypeDefault";
    public DbSet<Client> Client { get; set; }
    public DbSet<Resource> Resource { get; set; }
    public MK_DatabaseEntities(string _companycode)
        : base(GetConnectionString(_companycode))
    {
        if(_companycode == "Foo")
            this._dbType = "dbType1";
    }
    // Add model configurations
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Configurations
        .Add(new ClientConfiguration(_dbType))
        .Add(new ResourceConfiguration());
    }
    public static string GetConnectionString(string _companycode)
    {
        string _dbName = "MK_" + _companycode;
        // Start out by creating the SQL Server connection string
        SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder();
        sqlBuilder.DataSource = Properties.Settings.Default.ServerName;
        sqlBuilder.UserID = Properties.Settings.Default.ServerUserName;
        sqlBuilder.Password = Properties.Settings.Default.ServerPassword;
        // The name of the database on the server
        sqlBuilder.InitialCatalog = _dbName;
        sqlBuilder.IntegratedSecurity = false;
        sqlBuilder.ApplicationName = "EntityFramework";
        sqlBuilder.MultipleActiveResultSets = true;
        string sbstr = sqlBuilder.ToString();
        return sbstr;
    }
}

ClientConfiguration: Client的配置中,我在将属性映射到数据库列之前检查标志。

public class ClientConfiguration : EntityTypeConfiguration<Client>
{
    public ClientConfiguration(string _dbType)
    {
        HasKey(k => k.Id);
        Property(p => p.Id)
        .HasColumnName("ID")
        .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        if (_dbType == "dbType1")
        {
            Property(p => p.AbcId).HasColumnName("AbcID");
        }
        Property(p => p.FirstName).HasColumnName("FirstName");
        Property(p => p.LastName).HasColumnName("LastName");          
    }
}

客户端类:这就是我的Client类的样子,这里没有什么奇怪的。

public class Client : IIdentifiable
{
    public int Id { get; set; }
    public string AbcId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
public interface IIdentifiable
{
    int Id { get; }
}
备份解决方案是使用原始SQL查询来处理有问题的表,其余的使用ORM,但如果有一些我没有想到的方法来做到这一点,那将是非常棒的。现在我正在尝试实体框架,但我不反对尝试其他ORM,如果它可以做得更好。

使用实体框架(或其他ORM?)管理几个几乎相同的客户端数据库

Code First支持这种场景:

1)两个模型的通用实体:

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

2)表2的基本版本

public class Table2A
{
    public int Id { get; set; }
    public int Name2 { get; set; }
    public Table1 Table1 { get; set; }
}

3)表2的"扩展"版本继承了版本A,并添加了一个额外的列

public class Table2B : Table2A
{
    public int Fk { get; set; }
}

4)基本上下文,仅包括公共实体。请注意,这里有一个接受连接字符串的构造函数,因此没有无参数构造函数。这将强制继承上下文提供其特定的连接字符串。

public class CommonDbContext : DbContext
{
    public CommonDbContext(string connectionString)
        :base(connectionString)
    {
    }
    public IDbSet<Table1> Tables1 { get; set; }
}

5)上下文A,继承公共上下文,增加Table2A,忽略Table2B

public class DbContextA : CommonDbContext
{
    public DbContextA() : base("SimilarA") { } // connection for A
    public IDbSet<Table2A> Tables2A { get; set; }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Ignore<Table2B>(); // Ignore Table B
    }
}
  • 上下文B,继承了common,包括Table2B

    DbContextB: CommonDbContext{public DbContextB():base("SimilarB") {}//B的连接公共IDbSet表2b{获取;设置;}}

使用此设置,您可以实例化DbContextADbContextB。一个优点是它们都继承了CommonDbContext,所以无论具体实现是版本a还是版本B,您都可以使用这个基类的变量来访问公共实体。您只需要更改为具体类型来访问a或B的特定实体(本示例中的Table2ATable2B)。

您可以使用工厂或DI或其他方式根据DB获取所需的上下文。例如,这可能是您的工厂实现:

public class CommonDbContextFactory
{
    public static  CommonDbContext GetDbContext(string contextVersion)
    {
       switch (contextVersion)
       {
         case "A": 
             return new DbContextA();
         case "B":
             return new DbContextB();
         default:
             throw new ArgumentException("Missing DbContext", "contextVersion");
       }
    }
}

注意:这是工作样例代码。你当然可以根据你的具体情况调整它。我想让它保持简单,以展示它是如何工作的。对于您的情况,您可能需要更改工厂实现,并在A和B上下文构造函数中公开连接字符串,并在工厂方法

中提供它。

处理实体的不同类

处理每个DbContext的不同实体最简单的方法是使用多态性和/或泛型。

如果使用多态性,则需要实现使用基类类型(作为参数和返回类型)的方法。这些形参和变量将保存基类或派生类(Table2ATable2B)的实体。在这种情况下,每个上下文都将接收正确类型的实体,并且它将直接工作而不会出现问题。

问题是当你的应用是多层的,使用服务或是一个web应用。在这种情况下,当你使用基类的多态行为可能会丢失,你需要处理基类的实体。(例如,如果你让用户在web应用程序表单中编辑派生类的实体,表单只能处理基类的属性,当它被发送回来时,派生类的属性将丢失)在这种情况下,你需要智能地处理它(见下面的注释):

对于读取的目的,如果您有Table2B,则可以直接强制转换为Table2A。您可以实现Table2A的功能并直接使用它。也就是说,你可以返回基类的集合或单个值(在许多情况下隐式强制转换就足够了)。别再担心了。

对于插入/更新,您必须采取额外的步骤,但这并不太难。您需要在您的上下文中实现接收/返回Table2A参数的方法,或者在另一层,这取决于您的体系结构。例如,您可以使基本上下文抽象,并为此定义虚拟方法。(见下面的例子)。然后,您需要为每个特定情况制定正确的实现。

  • 如果你收到一个Table2A,但需要在Table2B中插入它,只需用AutoMapperValueInjecter将实体a映射到实体B,并使用默认值填充剩余的属性(注意AutoMapper和EF动态代理:它不会工作)。
  • 如果您收到Table2A并且需要更新Table2B,只需从DB中读取现有实体并重复映射过程(对于这种情况,ValueInjecter将比AutoMapper更少麻烦)。

这是一个非常简单的例子,但你需要根据你的具体情况调整它:

CommonDbContext类中,为基类型声明虚方法,如下所示:

public virtual Table2A GetTable2AById(int id);
public virtual void InsertTable2A(Table2A table);

你也可以使用泛型接口/方法,而不是抽象类/虚拟方法,像这样:

public T GetTable2AById<T>(int id)
{
   // The implementation
}

在这种情况下,您应该向T类型添加必要的约束,如where T: Table2A或您需要的约束(class new())。

在这种情况下并不确切地说多态性丢失了,因为您可以使用WCF或Web API制作多态Web服务,使您的UI适应实体的实际类(每种情况都有模板)等等。这取决于你需要什么或者想要实现什么。

我也经历过。

In all serious: dump EF;它会带来很多痛苦和折磨,没有任何好处。

你最终要做的(戴上我的算命帽)是,你将去掉所有基于ef的代码,创建一个抽象对象模型,然后编写一系列后端,将所有不同的数据库结构来回映射到这个干净的抽象对象模型。您将使用原始SQL或轻量级的东西,如Dapper或BLToolkit。