[英]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; 组; }}
使用此设置,您可以实例化DbContextA
或DbContextB
。 一个优点是两者都继承CommonDbContext
,因此,无论具体实现是版本A还是版本B,您都可以使用此基类的变量来访问公共实体。只需要更改为具体类型即可访问特定实体。 A或B(此示例中的Table2A
或Table2B
)。
您可以使用工厂或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将保存基类或派生类( Table2A
或Table2B
)的Table2B
。 在这种情况下,每个上下文都会收到正确类型的实体,并且可以直接工作而不会遇到麻烦。
问题是当您的应用程序是多层的,使用服务或是Web应用程序时。 在这种情况下,当您使用基类时,多态行为可能会丢失,并且您需要处理基类的实体。 (例如,如果让用户在Web应用程序表单中编辑派生类的实体,则该表单只能处理基类的属性,并且在将其回发时,派生类的属性将丢失)在这种情况下,您需要智能地处理它(请参阅下面的注释):
出于阅读目的,如果您具有Table2B
,则可以直接转换为Table2A
。 您可以实现Table2A
功能并直接使用它。 即,您可以返回基类的集合或单个值(在许多情况下,隐式转换就足够了)。 不用担心。
对于插入/更新 ,您必须采取额外的步骤,但这并不难。 您需要实现在上下文中或在另一层中接收/返回Table2A
参数的方法,具体取决于您的体系结构。 例如,您可以使基本上下文抽象,并为此定义虚拟方法。 (请参见下面的示例)。 然后,您需要针对每个特定案例进行正确的实现。
Table2A
但需要将其插入到Table2B
,则只需使用AutoMapper
或ValueInjecter
将实体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.