Resistencia de conexión y lógica de reintento
Nota:
Solo EF6 y versiones posteriores: las características, las API, etc. que se tratan en esta página se han incluido a partir de Entity Framework 6. Si usa una versión anterior, no se aplica parte o la totalidad de la información.
Las aplicaciones que se conectan a un servidor de bases de datos siempre han sido vulnerables a interrupciones de conexión debido a errores de back-end y inestabilidad de red. Sin embargo, en un entorno basado en LAN que se trabaja con servidores de bases de datos dedicados, estos errores son lo suficientemente poco habituales como para controlar esos errores. Con el aumento de los servidores de bases de datos basados en la nube, como Windows Azure SQL Database y las conexiones a través de redes menos confiables, ahora es más común que se produzcan interrupciones de conexión. Esto podría deberse a técnicas defensivas que usan las bases de datos en la nube para garantizar la imparcialidad del servicio, como la limitación de conexión, o la inestabilidad en la red que provoca tiempos de espera intermitentes y otros errores transitorios.
Resistencia de conexión hace referencia a la capacidad de EF de reintentar automáticamente los comandos que produzcan un error debido a estos saltos de conexión.
Estrategias de ejecución
El reintento de conexión se encarga de una implementación de la interfaz IDbExecutionStrategy. Las implementaciones de IDbExecutionStrategy serán responsables de aceptar una operación y, si se produce una excepción, determinar si un reintento es adecuado y volver a intentarlo si es así. Hay cuatro estrategias de ejecución que se incluyen con EF:
- DefaultExecutionStrategy: esta estrategia de ejecución no vuelve a intentar ninguna operación; es el valor predeterminado para las bases de datos que no sean sql Server.
- DefaultSqlExecutionStrategy: se trata de una estrategia de ejecución interna que se usa de forma predeterminada. Sin embargo, esta estrategia no reintentará todas las excepciones que podrían ser transitorias para informar a los usuarios de que podrían querer habilitar la resistencia de conexión.
- DbExecutionStrategy: esta clase es adecuada como clase base para otras estrategias de ejecución, incluidas sus propias personalizadas. Implementa una directiva de reintento exponencial, donde el reintento inicial se produce con un retraso cero y el retraso aumenta exponencialmente hasta que se alcanza el número máximo de reintentos. Esta clase tiene un método ShouldRetryOn abstracto que se puede implementar en estrategias de ejecución derivadas para controlar qué excepciones se deben reintentar.
- SqlAzureExecutionStrategy: esta estrategia de ejecución hereda de DbExecutionStrategy y reintentará las excepciones que se sabe que son posiblemente transitorias al trabajar con Azure SQL Database.
Nota:
Las estrategias de ejecución 2 y 4 se incluyen en el proveedor de Sql Server que se incluye con EF, que se encuentra en el ensamblado EntityFramework.SqlServer y están diseñados para trabajar con SQL Server.
Habilitación de una estrategia de ejecución
La manera más fácil de indicar a EF que use una estrategia de ejecución es con el método SetExecutionStrategy de la clase DbConfiguration :
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
}
}
Este código indica a EF que use SqlAzureExecutionStrategy al conectarse a SQL Server.
Configuración de la estrategia de ejecución
El constructor de SqlAzureExecutionStrategy puede aceptar dos parámetros, MaxRetryCount y MaxDelay. El recuento de MaxRetry es el número máximo de veces que la estrategia reintentará. MaxDelay es un TimeSpan que representa el retraso máximo entre reintentos que usará la estrategia de ejecución.
Para establecer el número máximo de reintentos en 1 y el retraso máximo en 30 segundos, ejecutaría lo siguiente:
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
SetExecutionStrategy(
"System.Data.SqlClient",
() => new SqlAzureExecutionStrategy(1, TimeSpan.FromSeconds(30)));
}
}
SqlAzureExecutionStrategy volverá a intentarlo al instante la primera vez que se produce un error transitorio, pero retrasará más tiempo entre cada reintento hasta que se supere el límite máximo de reintentos o el tiempo total alcanza el retraso máximo.
Las estrategias de ejecución solo reintentarán un número limitado de excepciones que suelen ser transitorias, seguirá siendo necesario controlar otros errores, así como detectar la excepción RetryLimitExceeded en caso de que un error no sea transitorio o tarde demasiado tiempo en resolverse.
Existen algunas limitaciones conocidas al usar una estrategia de ejecución de reintento:
No se admiten consultas de streaming
De forma predeterminada, EF6 y versiones posteriores almacenarán en búfer los resultados de la consulta en lugar de transmitirlos. Si desea que se transmitan los resultados, puede usar el método AsStreaming para cambiar una consulta LINQ to Entities a streaming.
using (var db = new BloggingContext())
{
var query = (from b in db.Blogs
orderby b.Url
select b).AsStreaming();
}
}
No se admite el streaming cuando se registra una estrategia de ejecución de reintento. Esta limitación existe porque la conexión podría quitarse de forma parcial a través de los resultados que se devuelven. Cuando esto ocurre, EF debe volver a ejecutar toda la consulta, pero no tiene ninguna manera confiable de saber qué resultados ya se han devuelto (los datos pueden haber cambiado desde que se envió la consulta inicial, los resultados pueden volver en un orden diferente, los resultados pueden no tener un identificador único, etc.).
No se admiten transacciones iniciadas por el usuario
Cuando haya configurado una estrategia de ejecución que produzca reintentos, hay algunas limitaciones en torno al uso de transacciones.
De forma predeterminada, EF realizará las actualizaciones de base de datos dentro de una transacción. No es necesario hacer nada para habilitar esto, EF siempre lo hace automáticamente.
Por ejemplo, en el código siguiente SaveChanges se realiza automáticamente dentro de una transacción. Si SaveChanges no se pudo realizar después de insertar uno de los nuevos sitios, la transacción se revertiría y no se aplicaría ningún cambio a la base de datos. El contexto también se deja en un estado que permite llamar a SaveChanges de nuevo para volver a intentar aplicar los cambios.
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();
}
Cuando no se usa una estrategia de ejecución de reintento, puede encapsular varias operaciones en una sola transacción. Por ejemplo, el código siguiente encapsula dos llamadas SaveChanges en una sola transacción. Si se produce un error en alguna parte de cualquiera de las operaciones, no se aplica ninguno de los cambios.
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();
}
}
Esto no se admite al usar una estrategia de ejecución de reintento porque EF no conoce ninguna operación anterior y cómo reintentarlas. Por ejemplo, si se produjo un error en el segundo SaveChanges, EF ya no tiene la información necesaria para reintentar la primera llamada a SaveChanges.
Solución: Estrategia de ejecución manual de llamadas
La solución consiste en usar manualmente la estrategia de ejecución y darle todo el conjunto de lógica que se va a ejecutar, de modo que pueda volver a intentarlo todo si se produce un error en una de las operaciones. Cuando se ejecuta una estrategia de ejecución derivada de DbExecutionStrategy, suspenderá la estrategia de ejecución implícita usada en SaveChanges.
Tenga en cuenta que todos los contextos se deben construir dentro del bloque de código que se va a reintentar. Esto garantiza que estamos empezando con un estado limpio para cada reintento.
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();
}
}
});