连接复原能力和重试逻辑

注意

仅限 EF6 及更高版本 - 此页面中讨论的功能、API 等已引入实体框架 6。 如果使用的是早期版本,则部分或全部信息不适用。

由于后端故障和网络不稳定等问题,连接到数据库服务器的应用程序总是容易受到连接中断的影响。 但是,在针对专用数据库服务器运行的基于 LAN 的环境中,这些错误很少出现,以至于通常不需要额外的逻辑来处理这些故障。 随着基于云的数据库服务器(例如 Windows Azure SQL 数据库)的兴起以及通过不太可靠的网络进行连接,现在发生连接中断的情况更为常见。 这可能归因于云数据库用于确保服务公平性的防御技术(例如连接限制),或归因于导致间歇性超时和其他暂时性错误的网络不稳定问题。

连接复原能力是指 EF 能够自动重试由于这些连接中断而失败的任何命令。

执行策略

连接重试由 IDbExecutionStrategy 接口的实现处理。 IDbExecutionStrategy 的实现将负责接受操作,如果发生异常,则确定是否适合重试,如果适合,则重试。 EF 附带四种执行策略:

  1. DefaultExecutionStrategy:此执行策略不重试任何操作,它是 SQL Server 以外的数据库的默认值。
  2. DefaultSqlExecutionStrategy:这是默认使用的内部执行策略。 此策略完全不重试,但是,它将包装任何暂时性异常,以通知用户他们可能需要启用连接复原能力。
  3. DbExecutionStrategy:此类适合作为其他执行策略(包括你自己的自定义策略)的基类。 它实现指数重试策略,其中初始重试以零延迟发生,延迟呈指数增长,直到达到最大重试计数。 此类有一个抽象的 ShouldRetryOn 方法,可以在派生的执行策略中实现,以控制应该重试哪些异常。
  4. SqlAzureExecutionStrategy:此执行策略继承自 DbExecutionStrategy,并针对已知在使用 Azure SQL 数据库时可能发生的暂时性异常进行重试。

注意

执行策略 2 和 4 包含在 EF 附带的 SQL Server 提供程序中,该提供程序位于 EntityFramework.SqlServer 程序集中,旨在用于 SQL Server。

启用执行策略

指示 EF 使用执行策略的最简单方法是使用 DbConfiguration 类的 SetExecutionStrategy 方法:

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
    }
}

此代码指示 EF 在连接到 SQL Server 时使用 SqlAzureExecutionStrategy。

配置执行策略

SqlAzureExecutionStrategy 的构造函数可以接受两个参数:MaxRetryCount 和 MaxDelay。 MaxRetryCount 是策略将重试的最大次数。 MaxDelay 是一个 TimeSpan,表示执行策略将使用的两次重试之间的最大延迟。

若要将最大重试次数设置为 1,将最大延迟设置为 30 秒,请执行以下命令:

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetExecutionStrategy(
            "System.Data.SqlClient",
            () => new SqlAzureExecutionStrategy(1, TimeSpan.FromSeconds(30)));
    }
}

SqlAzureExecutionStrategy 将在第一次发生暂时性故障时立即重试,但每次重试的间隔时间会逐渐延长,直到超过最大重试限制或总时间达到最大延迟时间。

执行策略仅重试有限数量的异常(通常是暂时性异常),你仍然需要处理其他错误以及捕获 RetryLimitExceeded 异常,以防错误不是暂时性的或需要很长时间才能自行解决。

使用重试执行策略时存在一些已知限制:

不支持流式处理查询

默认情况下,EF6 及更高版本将缓冲查询结果,而不是流式处理查询结果。 如果你想要流式处理结果,可以使用 AsStreaming 方法将 LINQ to Entities 查询更改为流式处理。

using (var db = new BloggingContext())
{
    var query = (from b in db.Blogs
                orderby b.Url
                select b).AsStreaming();
    }
}

注册重试执行策略时不支持流式处理。 之所以存在此限制,是因为连接可能会在返回结果的过程中中断。 发生这种情况时,EF 需要重新运行整个查询,但没有可靠的方法知道哪些结果已经返回(数据自初始查询发送后可能已更改,结果可能以不同的顺序返回,结果可能没有唯一标识符,等等)。

不支持用户启动的事务

配置导致重试的执行策略后,事务的使用存在一些限制。

默认情况下,EF 将在事务中执行所有数据库更新。 你无需执行任何操作即可启用此功能,EF 始终会自动执行此操作。

例如,在以下代码中,SaveChanges 在事务中自动执行。 如果在插入新站点之一后,SaveChanges 失败,事务将回滚并且不会对数据库应用任何更改。 上下文也处于允许再次调用 SaveChanges 以重试应用更改的状态。

using (var db = new BloggingContext())
{
    db.Blogs.Add(new Site { Url = "http://msdn.com/data/ef" });
    db.Blogs.Add(new Site { Url = "http://blogs.msdn.com/adonet" });
    db.SaveChanges();
}

当不使用重试执行策略时,可以将多个操作包装在单个事务中。 例如,以下代码将两个 SaveChanges 调用包装在单个事务中。 如果任一操作的任何部分失败,则不应用任何更改。

using (var db = new BloggingContext())
{
    using (var trn = db.Database.BeginTransaction())
    {
        db.Blogs.Add(new Site { Url = "http://msdn.com/data/ef" });
        db.Blogs.Add(new Site { Url = "http://blogs.msdn.com/adonet" });
        db.SaveChanges();

        db.Blogs.Add(new Site { Url = "http://twitter.com/efmagicunicorns" });
        db.SaveChanges();

        trn.Commit();
    }
}

使用重试执行策略时不支持此操作,因为 EF 不知道以前的任何操作以及如何重试这些操作。 例如,如果第二次 SaveChanges 失败,则 EF 不再具有重试第一次 SaveChanges 调用所需的信息。

解决方案:手动调用执行策略

解决方案是手动使用执行策略,并为其提供要运行的整套逻辑,以便它可以在某个操作失败时重试所有操作。 当从 DbExecutionStrategy 派生的执行策略运行时,它将暂停 SaveChanges 中使用的隐式执行策略。

请注意,应在要重试的代码块内构造任何上下文。 这样可以确保每次重试都以干净的状态开始。

var executionStrategy = new SqlAzureExecutionStrategy();

executionStrategy.Execute(
    () =>
    {
        using (var db = new BloggingContext())
        {
            using (var trn = db.Database.BeginTransaction())
            {
                db.Blogs.Add(new Blog { Url = "http://msdn.com/data/ef" });
                db.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
                db.SaveChanges();

                db.Blogs.Add(new Blog { Url = "http://twitter.com/efmagicunicorns" });
                db.SaveChanges();

                trn.Commit();
            }
        }
    });