使用 Entity Framework 将相同的更改保存到多个数据库

Posted

技术标签:

【中文标题】使用 Entity Framework 将相同的更改保存到多个数据库【英文标题】:Save same changes to multiple databases with Entity Framework 【发布时间】:2019-02-05 15:31:05 【问题描述】:

我有 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();
    

这不起作用,但应该解释我想要实现的目标。

【问题讨论】:

我已经在下面发布了对手头问题的可能答案,但我想知道这个过程的目标是什么。通常你的开发环境连接到开发数据库,​​测试到测试,生产到生产......为什么你需要同时在所有 3 个数据库中插入数据? 【参考方案1】:

我还没有对 Oracle 数据库使用 EntityFramework,但它应该类似于连接 SQL Server,因为数据库名称是通过 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,从而指定要连接到哪个数据库。如果您查看上下文类,默认构造函数应该使用该参数调用基本构造函数。

public YourDbContext() : base("YourConnectionString") 

为了保存到多个数据库,您需要针对不同的 DbContext 实例工作,每个实例都具有不同的 ConnectionString 参数。因此,您的配置需要为每个 Db 列出几个不同的连接字符串,并且您可能希望 DbContext 类也允许其构造函数中的参数。

也许 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 调用可能会成功,而其他可能会失败,因此不同数据库中的数据可能不一致。您可能需要考虑跨多个数据库使用事务,但我自己还没有这样做。

【讨论】:

非常感谢您的帮助。我想出了一个解决方案并将其发布在下面。【参考方案2】:

在桑曼的帮助下,我找到了解决方案。

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.\ne.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\nex.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;
    

【讨论】:

以上是关于使用 Entity Framework 将相同的更改保存到多个数据库的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Entity Framework 4.1 与 MVC 3 登录一起使用

Entity Framework 5,多个模型,相同的实体

如何使用 Entity Framework Core 将 SQL 数据从一个表传输到另一个表

Entity Framework 6 的动态 MySQL 数据库连接

MVC5 Entity Framework学习之Entity Framework高级功能

csharp 使用Entity Framework将数据添加到表中。