【问题标题】:Manage several almost identical client databases using Entity framework (or other ORM?)使用实体框架(或其他 ORM?)管理几个几乎相同的客户端数据库
【发布时间】:2014-06-13 07:15:17
【问题描述】:

我正在设计一个 ASP.NET Web 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;
    }
}

客户端配置: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,如果它可以做得更好。

【问题讨论】:

  • 你能有一个工厂来生成不同公司代码的模型配置吗?
  • 你的意思是像下面建议的@JotaBe?是的,我想这可能会奏效。

标签: c# asp.net asp.net-mvc entity-framework orm


【解决方案1】:

使用 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

    公共类 DbContextB:CommonDbContext { public DbContextB() :base("SimilarB") { } // B 的连接 公共 IDbSet Tables2B { 获取;放; } }

使用此设置,您可以实例化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 的不同实体的最简单方法是使用多态性和/或泛型。

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

问题在于您的应用是多层应用、使用服务还是网络应用。在这种情况下,当您使用基类时,多态行为可能会丢失,您需要处理基类的实体。 (比如你让用户在web app表单中编辑一个派生类的实体,表单只能处理基类的属性,回发时派生类的属性会丢失)在这种情况下,您需要明智地处理它(见下面的注释):

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

对于插入/更新,您必须采取额外的步骤,但这并不太难。您需要在您的上下文或另一层中实现接收/返回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 适应您的实体的真实类(每个案例都有模板)等等。这取决于您需要或想要实现的目标。

【讨论】:

  • 这可能行得通,它似乎对我们的情况足够通用,我唯一担心的是,如果我应该使用 Table2ATable2B 在一个添加或返回或其他任何东西。但我认为这没有任何帮助,因为它们需要有所不同。我会考虑到这一点并进行测试。
  • 这个问题很容易解决。阅读我的更新答案:我在底部添加了额外信息。
【解决方案2】:

去过那里,做到了。

严肃地说:在这种特定情况下转储 EF;它会带来很多痛苦和痛苦而没有任何好处。

你最终会做的(戴上我的算命师帽)是你将删除所有基于 EF 的代码,创建一个抽象对象模型,然后编写一系列后端来映射所有不同的数据库结构来回到所述干净的抽象对象模型。您将使用原始 SQL 或诸如 Dapper 或 BLToolkit 之类的轻量级工具。

【讨论】:

  • 有趣,我担心它会变成这样,你有什么好的论据可以用来说服我的同事不使用 ORM 的想法吗? ;) 我将研究如何干净地/可维护地做类似的事情,我最近一直在用 PHP 编码,所以我的 C# 生锈了。
猜你喜欢
  • 1970-01-01
  • 2021-04-29
  • 2023-03-24
  • 1970-01-01
  • 2017-07-13
  • 2011-01-29
  • 2019-12-19
  • 2012-04-03
  • 2014-04-27
相关资源
最近更新 更多