Entity Framework con NOLOCK

Respuestas:

207

No, pero puede iniciar una transacción y establecer el nivel de aislamiento para leer sin confirmar . Básicamente, esto hace lo mismo que NOLOCK, pero en lugar de hacerlo por tabla, lo hará para todo lo que esté dentro del alcance de la transacción.

Si eso suena como lo que quieres, así es como puedes hacerlo ...

//declare the transaction options
var transactionOptions = new System.Transactions.TransactionOptions();
//set it to read uncommited
transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
//create the transaction scope, passing our options in
using (var transactionScope = new System.Transactions.TransactionScope(
    System.Transactions.TransactionScopeOption.Required, 
    transactionOptions)
)

//declare our context
using (var context = new MyEntityConnection())
{
    //any reads we do here will also read uncomitted data
    //...
    //...
    //don't forget to complete the transaction scope
    transactionScope.Complete();
}
Doctor jones
fuente
Excelente @DoctaJonez ¿Se introdujo algo nuevo en EF4 para esto?
FMFF
@FMFF No sé si se introdujo algo nuevo para EF4. Sin embargo, sé que el código anterior funciona con EFv1 y superior.
Doctor Jones
¿Cuál sería la consecuencia? si alguien omite la transacciónScope.Complete () en el bloque mencionado anteriormente? ¿Crees que debería presentar otra pregunta para esto?
Eakan Gopalakrishnan
@EakanGopalakrishnan Si no se llama a este método, se anula la transacción, porque el administrador de la transacción interpreta esto como una falla del sistema o excepciones dentro del alcance de la transacción. (Tomado de MSDN msdn.microsoft.com/en-us/library/… )
Doctor Jones
1
@JsonStatham se ha agregado en esta solicitud de extracción , que es para el hito 2.1.0
Doctor Jones
83

Los métodos de extensión pueden facilitar esto

public static List<T> ToListReadUncommitted<T>(this IQueryable<T> query)
{
    using (var scope = new TransactionScope(
        TransactionScopeOption.Required, 
        new TransactionOptions() { 
            IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
    {
        List<T> toReturn = query.ToList();
        scope.Complete();
        return toReturn;
    }
}

public static int CountReadUncommitted<T>(this IQueryable<T> query)
{
    using (var scope = new TransactionScope(
        TransactionScopeOption.Required, 
        new TransactionOptions() { 
            IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
    {
        int toReturn = query.Count();
        scope.Complete();
        return toReturn;
    }
}
Alexandre
fuente
Usar esto en mi proyecto da como resultado que el grupo de conexiones se utilice por completo, lo que resulta en una excepción. No puedo entender por qué. ¿Alguien más tiene estos problemas? ¿Alguna sugerencia?
Ben Tidman
1
No hay problemas, Ben, no olvides SIEMPRE deshacerte de tu contexto de conexión.
Alexandre
Pude reducir el problema para excluir el alcance de la transacción como una posible causa. Gracias. Tenía algo que ver con algunas cosas de reintento de conexión que tenía en mi constructor.
Ben Tidman
Creo que el alcance debe ser TransactionScopeOption.Suppress
CodeGrue
@Alexandre ¿Qué pasaría si hiciera esto dentro de otra transacción ReadCommitted? Por ejemplo, generé una transacción para comenzar a guardar datos, pero ahora estoy consultando más datos y, por lo tanto, generando una transacción no confirmada de lectura. ¿Llamar a esto "Completo" también completará mi transacción externa? Amablemente aconseje :)
Jason Loki Smith
27

Si necesita algo en general, la mejor manera que encontramos de que es menos intrusivo que iniciar un alcance de transacciones cada vez, es simplemente establecer el nivel de aislamiento de transacción predeterminado en su conexión después de haber creado su contexto de objeto ejecutando este comando simple:

this.context.ExecuteStoreCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");

http://msdn.microsoft.com/en-us/library/aa259216(v=sql.80).aspx

Con esta técnica, pudimos crear un proveedor de EF simple que crea el contexto para nosotros y realmente ejecuta este comando cada vez para todo nuestro contexto para que siempre estemos en "lectura no comprometida" de forma predeterminada.

Frank, Alemania
fuente
2
Establecer el nivel de aislamiento de la transacción solo no tendrá ningún efecto. En realidad, debe ejecutarse dentro de una transacción para que tenga efecto. La documentación de MSDN para los estados READ UNCOMMITTED Transactions running at the READ UNCOMMITTED level do not issue shared locks. Esto implica que debe estar ejecutándose dentro de una transacción para obtener el beneficio. (tomado de msdn.microsoft.com/en-gb/library/ms173763.aspx ). Su enfoque puede ser menos intrusivo, pero no logrará nada si no utiliza una transacción.
Doctor Jones
3
La documentación de MSDN dice: "Controla el comportamiento de bloqueo y control de versiones de filas de las instrucciones Transact-SQL emitidas por una conexión a SQL Server". y "Especifica que las declaraciones pueden leer filas que han sido modificadas por otras transacciones pero que aún no se han confirmado". Esta declaración que escribí afecta CADA declaración SQL, ya sea dentro de una transacción o no. No me gusta contradecir a las personas en línea, pero está claramente equivocado en eso en base al uso de esta declaración en un entorno de producción grande. ¡No asumas cosas, pruébalas!
Frank.Germain
Los probé, tenemos un entorno de alta carga donde no realizar consultas dentro de uno de estos ámbitos de transacción (y una transacción coincidente) resultará en un punto muerto. Mis observaciones se realizaron en un servidor SQL 2005, por lo que no sé si el comportamiento ha cambiado desde entonces. Por lo tanto, recomendaría esto; si especifica un nivel de aislamiento de lectura no confirmada pero continúa experimentando puntos muertos, intente colocar sus consultas dentro de una transacción. Si no experimentas puntos muertos sin crear una transacción, entonces es justo.
Doctor Jones
3
@DoctorJones: con respecto a Microsoft SQL Server, todas las consultas son inherentemente transacciones. Especificar una transacción explícita es solo un medio de agrupar 2 o más declaraciones en la misma transacción para que puedan considerarse una unidad atómica de trabajo. El SET TRANSACTION ISOLATION LEVEL...comando afecta a una propiedad de nivel de conexión y, por lo tanto, afecta a todas las declaraciones SQL realizadas desde ese punto en adelante (para ESA conexión), a menos que se anule una sugerencia de consulta. Este comportamiento ha existido desde al menos SQL Server 2000, y probablemente antes.
Solomon Rutzky
55
@DoctorJones - Echa un vistazo a: msdn.microsoft.com/en-us/library/ms173763.aspx . Aquí hay una prueba. En SSMS, abrir una consulta (# 1) y ejecute: CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Abrir otra consulta (# 2) y de ejecución: SELECT * FROM ##Test;. SELECT no regresará ya que está siendo bloqueado por la transacción aún abierta en la pestaña # 1 que está usando un bloqueo exclusivo. Cancele SELECCIONAR en el n. ° 2. Ejecutar SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDuna vez en la pestaña # 2. Ejecute nuevamente SELECT en la pestaña # 2 y volverá. Asegúrese de ejecutar ROLLBACKen la pestaña # 1.
Solomon Rutzky
21

Aunque estuve absolutamente de acuerdo en que usar el nivel de aislamiento de transacciones no confirmadas de lectura es la mejor opción, pero en algún momento se obligó a usar la sugerencia de NOLOCK a pedido del gerente o cliente y no se aceptaron razones en contra de esto.

Con Entity Framework 6 puede implementar su propio DbCommandInterceptor de esta manera:

public class NoLockInterceptor : DbCommandInterceptor
{
    private static readonly Regex _tableAliasRegex = 
        new Regex(@"(?<tableAlias>AS \[Extent\d+\](?! WITH \(NOLOCK\)))", 
            RegexOptions.Multiline | RegexOptions.IgnoreCase);

    [ThreadStatic]
    public static bool SuppressNoLock;

    public override void ScalarExecuting(DbCommand command, 
        DbCommandInterceptionContext<object> interceptionContext)
    {
        if (!SuppressNoLock)
        {
            command.CommandText = 
                _tableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
        }
    }

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        if (!SuppressNoLock)
        {
            command.CommandText = 
                _tableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
        }
    }
}

Con esta clase en su lugar, puede aplicarla al inicio de la aplicación:

DbInterception.Add(new NoLockInterceptor());

Y apague condicionalmente la adición de NOLOCKpistas en las consultas para el hilo actual:

NoLockInterceptor.SuppressNoLock = true;
Yuriy Rozhovetskiy
fuente
Me gusta esta solución, aunque modifiqué ligeramente la expresión regular para:
Russ
2
(? <tableAlias>] AS [Extent \ d +] (?! WITH (NOLOCK))) para evitar agregar nolock a la tabla derivada que causa un error. :)
Russ
Establecer SuppressNoLock en el nivel de hilo es una forma conveniente, pero es fácil olvidarse de desarmar el booleano, debe usar una función que devuelva IDisposable, el método Dispose puede establecer el bool en falso nuevamente. Además, ThreadStatic no es realmente compatible con async / await: stackoverflow.com/questions/13010563/…
Jaap
O, si prefiere utilizar el NIVEL DE AISLAMIENTO: public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if (!SuppressNoLock) command.CommandText = $"SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;{Environment.NewLine}{command.CommandText}"; base.ReaderExecuting(command, interceptionContext); }
Adi
También agrega nolock a las funciones de la base de datos. ¿Cómo evitar las funciones?
Ivan Lewis el
9

Mejorar la respuesta aceptada del Doctor Jones y usar PostSharp ;

Primero " ReadUncommitedTransactionScopeAttribute "

[Serializable]
public class ReadUncommitedTransactionScopeAttribute : MethodInterceptionAspect
{
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        //declare the transaction options
        var transactionOptions = new TransactionOptions();
        //set it to read uncommited
        transactionOptions.IsolationLevel = IsolationLevel.ReadUncommitted;
        //create the transaction scope, passing our options in
        using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
        {
            //declare our context
            using (var scope = new TransactionScope())
            {
                args.Proceed();
                scope.Complete();
            }
        }
    }
}

Entonces, cuando lo necesites,

    [ReadUncommitedTransactionScope()]
    public static SomeEntities[] GetSomeEntities()
    {
        using (var context = new MyEntityConnection())
        {
            //any reads we do here will also read uncomitted data
            //...
            //...

        }
    }

Ser capaz de agregar "NOLOCK" con un interceptor también es bueno, pero no funcionará cuando se conecte a otros sistemas de bases de datos como Oracle como tal.

myuce
fuente
6

Para evitar esto, creo una vista en la base de datos y aplico NOLOCK en la consulta de la vista. Luego trato la vista como una tabla dentro de EF.

Ryan Galloway
fuente
4

Con la introducción de EF6, Microsoft recomienda usar el método BeginTransaction ().

Puede usar BeginTransaction en lugar de TransactionScope en EF6 + y EF Core

using (var ctx = new ContractDbContext())
using (var transaction = ctx.Database.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted))
{
    //any reads we do here will also read uncommitted data
}
Ali
fuente
2

No, no realmente: Entity Framework es básicamente una capa bastante estricta sobre su base de datos real. Sus consultas están formuladas en ESQL (Entity SQL), que está dirigido en primer lugar a su modelo de entidad, y dado que EF admite múltiples backends de base de datos, realmente no puede enviar SQL "nativo" directamente a su backend.

La sugerencia de consulta NOLOCK es una cosa específica de SQL Server y no funcionará en ninguna de las otras bases de datos compatibles (a menos que también hayan implementado la misma sugerencia, lo cual dudo mucho).

Bagazo

marc_s
fuente
Esta respuesta está desactualizada: puede usar NOLOCK como otros han mencionado, y puede ejecutar SQL "nativo" usando Database.ExecuteSqlCommand()o DbSet<T>.SqlQuery().
Dunc
1
@Dunc: gracias por el voto negativo - por cierto: NO deberías usar de (NOLOCK)todos modos - mira Bad Habits para patear - poniendo NOLOCK en todas partes - NO SE RECOMIENDA usar esto en todas partes - ¡todo lo contrario!
marc_s
0

Una opción es usar un procedimiento almacenado (similar a la solución de vista propuesta por Ryan) y luego ejecutar el procedimiento almacenado desde EF. De esta manera, el procedimiento almacenado realiza la lectura sucia mientras EF simplemente canaliza los resultados.

Rafiki
fuente