我正在构建一个ASP.NET Web API原型,需要与几个几乎完全相同的数据库进行通信。我们的每个客户都有自己的数据库结构实例,但有些客户专门与其他系统集成。因此,例如在一个数据库中,Client
表可能具有列AbcID
来引用另一个系统中的表,但其他数据库将不具有此列。除此之外,两个表的名称和列相同。列也可以具有不同的长度,例如varchar(50)
而不是varchar(40)
。在一些数据库中,可以有一个额外的表。我首先专注于解决不同的列问题。
我希望使用ORM来处理API的数据访问层,现在我正在尝试使用Entity框架。我已经解决了如何从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,如果那个可以做得更好。
答案 0 :(得分:0)
去过那里,做到了。
严肃地说:在这种特殊情况下转储EF;它将带来许多痛苦和痛苦,没有任何好处。
你最终会做什么(把我的Fortuneteller Hat放在上面)是你会删掉所有基于EF的代码,创建一个抽象对象模型,然后编写一系列后端来映射所有各种数据库结构来回干净的抽象对象模型。你要么使用原始SQL,要么像Dapper或BLToolkit那样轻量级。
答案 1 :(得分:0)
使用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)&#34;扩展&#34;表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
public class DbContextB:CommonDbContext { public DbContextB():base(&#34; SimilarB&#34;){} // 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
的不同实体的最简单方法是使用多态和/或泛型。
如果使用多态,则需要实现使用基类类型的方法(作为参数和返回类型)。此参数和变量将包含基类或派生类(Table2A
或Table2B
)的实体。在这种情况下,每个上下文将接收一个正确类型的实体,它将直接工作而没有麻烦。
问题在于您的应用是多层次的,使用服务还是网络应用。在这种情况下,当您使用基类时,多态行为可能会丢失,并且您需要处理基类的实体。 (例如,如果您让用户在Web应用程序表单中编辑派生类的实体,则表单只能处理基类的属性,并且当它被回发时,派生类的属性将丢失)在这种情况下,您需要智能地处理它(见下面的注释):
出于阅读的目的,如果您有Table2B
,则可直接转为Table2A
。您可以实现Table2A
的功能并直接使用它。即您可以返回基类的集合或单个值(在许多情况下,隐式转换就足够了)。不再担心。
要插入/更新,您必须采取额外步骤,但这并不太难。您需要实现在上下文或其他层中接收/返回Table2A
参数的方法,具体取决于您的体系结构。例如,您可以使基本上下文成为抽象,并为此定义虚拟方法。 (见下面的例子)。然后,您需要为每个特定案例制定正确的实施方案。
Table2A
但需要在Table2B
中插入,只需将实体A映射到包含AutoMapper
或ValueInjecter
的实体B,并使用默认值填充其余属性值(谨防AutoMapper
和EF动态代理:它不会工作)。Table2A
并需要更新Table2B
,只需从数据库中读取现有实体并重复映射过程(ValueInjecter
将比{{1}麻烦一点也适用于这种情况)。这是一个可以做什么的非常简单的例子,但你需要根据你的具体情况进行调整:
在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
)。
注意在这种情况下,确切地说多态性丢失并不准确,因为您可以使用WCF或Web API实现多态Web服务,使您的UI适应真实您的实体的类(每个案例都有模板)等等。这取决于你需要或想要达到的目标。