使用 Entity Framework 将相同的更改保存到多个数据库
Save same changes to multiple databases with Entity Framework
我有 3 个 Oracle 数据库;生产、测试、开发。在大多数情况下,它们都是相同的。在我的应用程序中,我希望将更改应用于多个数据库。例如:
using (var context = new Context())
{
context.People.Add(new Person { name = "sean" });
context.SaveChanges();
}
然后我尝试覆盖 SaveChanges 方法并通过这样做保存到多个数据库:
public void SaveChanges(int auditPersonNumber)
{
OracleCredentials.Default.Server = "VDev";
base.SaveChanges();
OracleCredentials.Default.Server = "VTest";
base.SaveChanges();
OracleCredentials.Default.Server = "VProd";
base.SaveChanges();
}
这没有用,但应该可以解释我想要实现的目标。
我还没有针对 Oracle 数据库使用过 EntityFramework,但它应该类似于针对 SQL 服务器的连接,因为数据库名称是通过 ConnectionString 指定的。您的项目应该有一个配置文件(web.config、app.config,或者如果它是一个 .NET Core 应用程序,它可能在 appsettings.json 中),其中包含该 ConnectionString。
例如:
<add name="YourConnectionString" providerName="YourOracleProviderName" connectionString="User Id=test;Password=testpassword;Data Source=eftest" />
DbContext 基本构造函数接受一个字符串参数,该参数指定它应该使用哪个 ConnectionString,从而指定要连接到哪个数据库。如果您查看上下文 class,默认构造函数应该使用该参数调用基本构造函数。
public YourDbContext() : base("YourConnectionString") {}
为了保存到多个数据库,您需要处理不同的 DbContext 实例,每个实例都有不同的 ConnectionString 参数。因此,您的配置需要为每个 Db 列出一些不同的连接字符串,并且您可能希望 DbContext class 也允许其构造函数中的参数。
也许 SaveChanges 方法实现可以实例化您需要使用的其他 DbContext:
public void SaveChanges(int auditPersonNumber)
{
using (var context = new Context("OtherConnectionString1"))
{
// apply same changes
context.SaveChanges();
}
using (var context = new Context("OtherConnectionString2"))
{
// apply same changes
context.SaveChanges();
}
base.SaveChanges();
}
至于应用相同的更改,我希望您可以从 DbContext ChangeTracker 中读取它们。这里有关于使用 EF Core 的解释,但在早期版本中它是相似的:http://www.entityframeworktutorial.net/efcore/changetracker-in-ef-core.aspx
另请注意,对 OtherConnectionString1 的 SaveChanges 调用可能会成功,而其他调用可能会失败,因此不同数据库中的数据可能不一致。您可能需要研究跨多个数据库使用事务,但我自己还没有这样做。
感谢桑曼的帮助,我找到了解决办法。
public class Context : Shared.Data.Context
{
new public void SaveChanges(int auditPersonNumber)
{
var errors = string.Empty;
var testConnectionString = "ConnectionString";
var developmentConnectionString = "ConnectionString";
//Save to test database
if (SecurityMaintenanceUser.ApplyToTest)
errors = ApplyToDatabase(testConnectionString, auditPersonNumber, "Test");
if (!string.IsNullOrWhiteSpace(errors))
errors += "\n\n";
//Save to development database
if (SecurityMaintenanceUser.ApplyToDevelopment)
errors += ApplyToDatabase(developmentConnectionString, auditPersonNumber, "Development");
if (!string.IsNullOrWhiteSpace(errors))
MessageBox.Show(errors, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
//Save to production database
base.SaveChanges(auditPersonNumber);
}
private string ApplyToDatabase(string connectionString, int auditPersonNumber, string server)
{
try
{
using (var context = new Context(connectionString))
{
context.Configuration.ValidateOnSaveEnabled = false;
foreach (var entry in ChangeTracker.Entries())
{
var dataSet = context.Set(entry.Entity.GetType());
if (entry.State == EntityState.Added)
{
dataSet.Add(entry.Entity);
}
else if (entry.State == EntityState.Deleted)
{
var contextEntity = dataSet.Find(GetPrimaryKeyValues(entry));
context.DeleteEntity(contextEntity, auditPersonNumber);
}
else if (entry.State == EntityState.Modified)
{
var contextEntity = dataSet.Find(GetPrimaryKeyValues(entry));
context.Entry(CopyProperties(entry.Entity, contextEntity)).State = EntityState.Modified;
}
}
context.SaveChanges(auditPersonNumber);
return string.Empty;
}
}
catch (Exception e)
{
return $"Failed to apply database changes to {server}.\n{e.GetFullMessage()}";
}
}
private object CopyProperties(object source, object destination)
{
if (source == null || destination == null)
throw new Exception("Source or/and Destination Objects are null");
var typeDest = destination.GetType();
var typeSrc = source.GetType();
foreach (var srcProp in typeSrc.GetProperties())
{
if (srcProp.Name == "Type" || srcProp.Name == "AuthenticationLog")
continue;
//This blocks any complex objects attached to the entity, will need to be changed for your application
if (srcProp.PropertyType.FullName.Contains("Library.Shared"))
continue;
if (!srcProp.CanRead)
continue;
var targetProperty = typeDest.GetProperty(srcProp.Name);
if (targetProperty == null)
continue;
if (!targetProperty.CanWrite)
continue;
if (targetProperty.GetSetMethod(true)?.IsPrivate == true)
continue;
if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
continue;
if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
continue;
targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
}
return destination;
}
private object GetPrimaryKeyValues(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
return objectStateEntry.EntityKey.EntityKeyValues[0].Value;
}
public static string GetFullMessage(this Exception ex)
{
return ex.InnerException == null ? ex.Message : $"{ex.Message}\n{ex.InnerException.GetFullMessage()}";
}
public static string Replace(this string source, string oldString, string newString, StringComparison comp)
{
int index = source.IndexOf(oldString, comp);
if (index >= 0)
{
source = source.Remove(index, oldString.Length);
source = source.Insert(index, newString);
}
if (source.IndexOf(oldString, comp) != -1)
source = Replace(source, oldString, newString, comp);
return source;
}
}
我有 3 个 Oracle 数据库;生产、测试、开发。在大多数情况下,它们都是相同的。在我的应用程序中,我希望将更改应用于多个数据库。例如:
using (var context = new Context())
{
context.People.Add(new Person { name = "sean" });
context.SaveChanges();
}
然后我尝试覆盖 SaveChanges 方法并通过这样做保存到多个数据库:
public void SaveChanges(int auditPersonNumber)
{
OracleCredentials.Default.Server = "VDev";
base.SaveChanges();
OracleCredentials.Default.Server = "VTest";
base.SaveChanges();
OracleCredentials.Default.Server = "VProd";
base.SaveChanges();
}
这没有用,但应该可以解释我想要实现的目标。
我还没有针对 Oracle 数据库使用过 EntityFramework,但它应该类似于针对 SQL 服务器的连接,因为数据库名称是通过 ConnectionString 指定的。您的项目应该有一个配置文件(web.config、app.config,或者如果它是一个 .NET Core 应用程序,它可能在 appsettings.json 中),其中包含该 ConnectionString。
例如:
<add name="YourConnectionString" providerName="YourOracleProviderName" connectionString="User Id=test;Password=testpassword;Data Source=eftest" />
DbContext 基本构造函数接受一个字符串参数,该参数指定它应该使用哪个 ConnectionString,从而指定要连接到哪个数据库。如果您查看上下文 class,默认构造函数应该使用该参数调用基本构造函数。
public YourDbContext() : base("YourConnectionString") {}
为了保存到多个数据库,您需要处理不同的 DbContext 实例,每个实例都有不同的 ConnectionString 参数。因此,您的配置需要为每个 Db 列出一些不同的连接字符串,并且您可能希望 DbContext class 也允许其构造函数中的参数。
也许 SaveChanges 方法实现可以实例化您需要使用的其他 DbContext:
public void SaveChanges(int auditPersonNumber)
{
using (var context = new Context("OtherConnectionString1"))
{
// apply same changes
context.SaveChanges();
}
using (var context = new Context("OtherConnectionString2"))
{
// apply same changes
context.SaveChanges();
}
base.SaveChanges();
}
至于应用相同的更改,我希望您可以从 DbContext ChangeTracker 中读取它们。这里有关于使用 EF Core 的解释,但在早期版本中它是相似的:http://www.entityframeworktutorial.net/efcore/changetracker-in-ef-core.aspx
另请注意,对 OtherConnectionString1 的 SaveChanges 调用可能会成功,而其他调用可能会失败,因此不同数据库中的数据可能不一致。您可能需要研究跨多个数据库使用事务,但我自己还没有这样做。
感谢桑曼的帮助,我找到了解决办法。
public class Context : Shared.Data.Context
{
new public void SaveChanges(int auditPersonNumber)
{
var errors = string.Empty;
var testConnectionString = "ConnectionString";
var developmentConnectionString = "ConnectionString";
//Save to test database
if (SecurityMaintenanceUser.ApplyToTest)
errors = ApplyToDatabase(testConnectionString, auditPersonNumber, "Test");
if (!string.IsNullOrWhiteSpace(errors))
errors += "\n\n";
//Save to development database
if (SecurityMaintenanceUser.ApplyToDevelopment)
errors += ApplyToDatabase(developmentConnectionString, auditPersonNumber, "Development");
if (!string.IsNullOrWhiteSpace(errors))
MessageBox.Show(errors, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
//Save to production database
base.SaveChanges(auditPersonNumber);
}
private string ApplyToDatabase(string connectionString, int auditPersonNumber, string server)
{
try
{
using (var context = new Context(connectionString))
{
context.Configuration.ValidateOnSaveEnabled = false;
foreach (var entry in ChangeTracker.Entries())
{
var dataSet = context.Set(entry.Entity.GetType());
if (entry.State == EntityState.Added)
{
dataSet.Add(entry.Entity);
}
else if (entry.State == EntityState.Deleted)
{
var contextEntity = dataSet.Find(GetPrimaryKeyValues(entry));
context.DeleteEntity(contextEntity, auditPersonNumber);
}
else if (entry.State == EntityState.Modified)
{
var contextEntity = dataSet.Find(GetPrimaryKeyValues(entry));
context.Entry(CopyProperties(entry.Entity, contextEntity)).State = EntityState.Modified;
}
}
context.SaveChanges(auditPersonNumber);
return string.Empty;
}
}
catch (Exception e)
{
return $"Failed to apply database changes to {server}.\n{e.GetFullMessage()}";
}
}
private object CopyProperties(object source, object destination)
{
if (source == null || destination == null)
throw new Exception("Source or/and Destination Objects are null");
var typeDest = destination.GetType();
var typeSrc = source.GetType();
foreach (var srcProp in typeSrc.GetProperties())
{
if (srcProp.Name == "Type" || srcProp.Name == "AuthenticationLog")
continue;
//This blocks any complex objects attached to the entity, will need to be changed for your application
if (srcProp.PropertyType.FullName.Contains("Library.Shared"))
continue;
if (!srcProp.CanRead)
continue;
var targetProperty = typeDest.GetProperty(srcProp.Name);
if (targetProperty == null)
continue;
if (!targetProperty.CanWrite)
continue;
if (targetProperty.GetSetMethod(true)?.IsPrivate == true)
continue;
if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
continue;
if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
continue;
targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
}
return destination;
}
private object GetPrimaryKeyValues(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
return objectStateEntry.EntityKey.EntityKeyValues[0].Value;
}
public static string GetFullMessage(this Exception ex)
{
return ex.InnerException == null ? ex.Message : $"{ex.Message}\n{ex.InnerException.GetFullMessage()}";
}
public static string Replace(this string source, string oldString, string newString, StringComparison comp)
{
int index = source.IndexOf(oldString, comp);
if (index >= 0)
{
source = source.Remove(index, oldString.Length);
source = source.Insert(index, newString);
}
if (source.IndexOf(oldString, comp) != -1)
source = Replace(source, oldString, newString, comp);
return source;
}
}