【问题标题】:General pattern for failing over from one database to another using Entity Framework?使用实体框架从一个数据库故障转移到另一个数据库的一般模式?
【发布时间】:2014-06-20 09:32:56
【问题描述】:

我们有一个企业数据库,可以通过世界各地的许多站点进行复制。我们希望我们的应用程序尝试连接到本地站点之一,如果该站点关闭,我们希望它回退到企业数据库。我们希望我们的每个数据库操作都有这种行为。

我们正在使用实体框架、C# 和 SQL Server。

起初我希望我可以在连接字符串中指定一个“故障转移伙伴”,但这仅适用于镜像数据库环境,但事实并非如此。我还研究了编写自定义 IDbExecutionStrategy。但是这些策略只允许您指定重试失败的数据库操作的模式。它不允许您以任何方式更改操作,例如将其定向到新连接。

那么,除了围绕我们的许多数据库操作重复重试逻辑之外,您是否知道处理此类操作的任何好的模式?


2014-05-14 更新:

我将根据已经提出的一些建议进行详细说明。

我有很多个地方的代码是这样的:

try
{
    using(var db = new MyDBContext(ConnectionString))
    {
        // Database operations here.
        // var myList = db.MyTable.Select(...), etc.
    }
}
catch(Exception ex)
{
    // Log exception here, perhaps rethrow.
}

建议我有一个例程,首先检查每个连接字符串并返回第一个成功连接的字符串。这是合理的。但我看到的一些错误是操作超时,连接工作但数据库存在问题,使其无法完成操作。

我正在寻找的是一种模式,我可以用它来封装工作单元并说:“在第一个数据库上试试这个。如果它因任何原因失败,请回滚并在第二个数据库上尝试它。如果那样失败,在第三次尝试,依此类推,直到操作成功或您没有更多的数据库。”我很确定我可以自己滚动(如果可以,我会发布结果),但我希望可能有一种已知的方法来解决这个问题。

【问题讨论】:

  • 我可以要求将其移至 dba.stackexchange.com,但我真的认为这更像是一个编程/实体框架问题,而不是数据库管理问题。我弄错了吗?
  • 是的,我可以看到。忽略我之前的陈述:)
  • 那么您的交易方法是什么?事务中失败的操作是否应该在事务中间切换到备用数据库?
  • 我在这里可能是错的,但我认为这通常属于负载均衡器的范围。
  • 是不是像1)打开一个要连接的数据库的连接1st。检查 Connection.State 属性,如果连接是打开的/如果没有打开到您的企业数据库的连接是否良好?

标签: entity-framework database-design


【解决方案1】:

如何使用一些依赖注入系统,如 autofac 并在那里为新的上下文对象注册一个工厂 - 它将执行首先尝试连接到本地的逻辑,如果发生故障,它将连接到企业数据库。然后它将返回准备好的 DbContext 对象。该工厂将通过依赖注入系统提供给所有需要它的对象 - 他们将使用它来创建上下文并在不再需要它们时将其处理掉。

【讨论】:

  • 感谢您的回复。这可能是我正在寻找的开始,但我正在寻找可以在操作过程中处理故障的东西。我已经更新了问题以澄清。
【解决方案2】:

" 我们希望我们的应用程序尝试连接到本地站点之一,如果该站点出现故障,我们希望它回退到企业数据库。我们希望在每个数据库操作中都有这种行为。 "

如果您的应用在数据库上是严格只读的,并且数据一致性对您的应用/用户而言并非绝对重要,那么只需尝试连接直到找到可操作的站点。正如 M.Ali 在他的评论中所建议的那样。

否则,我建议您立即停止按照这些思路进行思考,因为您只是在一条死胡同上以 90 英里/小时的速度行驶。正如 Viktor Zychla 在他的评论中所建议的那样。

【讨论】:

  • 感谢您的回复。我同意,如果我关心跨越多个工作单元的交易,我所寻求的将是不切实际的。而且我也同意我可以在每个 UOW 周围放置包装器逻辑以尝试连接,然后使用第一个有效的数据库。但我想要做的不止于此,而且我想避免与每个 UOW 重复这个逻辑。我将更新原始问题以进行详细说明。
【解决方案3】:

这是我最终实现的,粗略的笔触:

定义称为 UnitOfWorkMethod 的委托,它将在单个事务中在数据库上执行单个工作单元。它接受一个连接字符串,一个还返回一个值:

delegate T UnitOfWorkMethod<out T>(string connectionString);
delegate void UnitOfWorkMethod(string connectionString);

定义一个名为 ExecuteUOW 的方法,该方法将采用一个工作单元,方法尝试使用首选连接字符串执行它。如果失败,它会尝试使用下一个连接字符串执行它:

protected T ExecuteUOW<T>(UnitOfWorkMethod<T> method)
{
    // GET THE LIST OF CONNECTION STRINGS
    IEnumerable<string> connectionStringList = ConnectionStringProvider.GetConnectionStringList();

    // WHILE THERE ARE STILL DATABASES TO TRY, AND WE HAVEN'T DEFINITIVELY SUCCEDED OR FAILED
    var uowState = UOWStateEnum.InProcess;
    IEnumerator<string> stringIterator = connectionStringList.GetEnumerator();
    T returnVal = default(T);
    Exception lastException = null;
    string connectionString = null;
    while ((uowState == UOWStateEnum.InProcess) && stringIterator.MoveNext())
    {
        try
        {
            // TRY TO EXECUTE THE UNIT OF WORK AGAINST THE DB.
            connectionString = stringIterator.Current;
            returnVal = method(connectionString);
            uowState = UOWStateEnum.Success;
        }
        catch (Exception ex)
        {
            lastException = ex;
            // IF IT FAILED BECAUSE OF A TRANSIENT EXCEPTION,
            if (TransientChecker.IsTransient(ex))
            {
                // LOG THE EXCEPTION AND TRY AGAINST ANOTHER DB.
                Log.TransientDBException(ex, connectionString);
            }
                // ELSE
            else
            {
                // CONSIDER THE UOW FAILED.
                uowState = UOWStateEnum.Failed;
            }
        }
    }

    // LOG THE FAILURE IF WE HAVE NOT SUCCEEDED.
    if (uowState != UOWStateEnum.Success)
    {
        Log.ExceptionDuringDataAccess(lastException);
        returnVal = default(T);
    }
    return returnVal;
}

最后,我们为每个操作定义了我们的工作单元委托方法。这里是一个例子

UnitOfWorkMethod uowMethod =
    (providerConnectionString =>
     {
         using (var db = new MyContext(providerConnectionString ))
         {
             // Do my DB commands here.  They will roll back if exception thrown.
         }
     });
ExecuteUOW(uowMethod);

当调用 ExecuteUOW 时,它会在每个数据库上尝试委托,直到在所有数据库上成功或失败。

我将接受这个答案,因为它完全解决了原始问题中提出的所有问题。但是,如果有人提供和回答更优雅、更易于理解或纠正这一问题的缺陷,我会很乐意接受。

感谢所有回复的人。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-28
    • 2020-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多