繁体   English   中英

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

[英]Manage several almost identical client databases using Entity framework (or other ORM?)

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

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

我试图使用数据库优先方法建立双.edmx模型,但这会导致模型之间的类名冲突。 因此,我尝试了Code-first,并提出了解决方案(这是行不通的)。

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,只要它能做得更好。

去过也做过。

认真地说:在这种特定情况下,将EF丢弃; 它会带来很多痛苦和痛苦,无济于事。

您最终要做的(戴上我的算命先生的帽子)是剥去所有基于EF的代码,创建一个抽象的对象模型,然后编写一系列后端,这些后端将向后映射所有各种数据库结构,谈到干净的抽象对象模型。 您将使用原始SQL或轻巧的Dapper或BLToolkit之类的东西。

使用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继承了公共Table2B ,并包含Table2B

    公共类DbContextB:CommonDbContext {public DbContextB():base(“ SimilarB”){} // B的连接public IDbSet Tables2B {get; 组; }}

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

您可以使用工厂或DI或其他任何方式来获取所需的上下文,具体取决于数据库。 例如,这可能是您的工厂实现:

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的不同实体的最简单方法是使用多态和(或)泛型。

如果使用多态,则需要实现使用基类类型(作为参数和返回类型)的方法。 此参数和var将保存基类或派生类( Table2ATable2B )的Table2B 在这种情况下,每个上下文都会收到正确类型的实体,并且可以直接工作而不会遇到麻烦。

问题是当您的应用程序是多层的,使用服务或是Web应用程序时。 在这种情况下,当您使用基类时,多态行为可能会丢失,并且您需要处理基类的实体。 (例如,如果让用户在Web应用程序表单中编辑派生类的实体,则该表单只能处理基类的属性,并且在将其回发时,派生类的属性将丢失)在这种情况下,您需要智能地处理它(请参阅下面的注释):

出于阅读目的,如果您具有Table2B ,则可以直接转换为Table2A 您可以实现Table2A功能并直接使用它。 即,您可以返回基类的集合或单个值(在许多情况下,隐式转换就足够了)。 不用担心。

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

  • 如果收到Table2A但需要将其插入到Table2B ,则只需使用AutoMapperValueInjecter将实体A映射到实体B中,并用默认值填充其余属性(请注意AutoMapper和EF动态代理:它将不起作用)。
  • 如果收到Table2A并需要更新Table2B ,则只需从数据库中读取现有实体,然后重复映射过程即可(在这种情况下, 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适应实体的真实类(每种情况都有模板),依此类推。 。 这取决于您需要或想要实现的目标。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM