Estrutura de Entidades com NOLOCK

138

Como posso usar a NOLOCKfunção no Entity Framework? XML é a única maneira de fazer isso?

OneSmartGuy
fonte

Respostas:

207

Não, mas você pode iniciar uma transação e definir o nível de isolamento para ler descomprometido . Isso basicamente faz o mesmo que o NOLOCK, mas, em vez de fazê-lo por tabela, o fará para tudo dentro do escopo da transação.

Se isso soa como o que você deseja, veja como você pode fazê-lo ...

//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
fonte
Excelente @DoctaJonez Alguma novidade foi introduzida no EF4 para isso?
FMFF 21/02/12
@FMFF Não sei se algo novo foi introduzido para o EF4. Eu sei que o código acima funciona com EFv1 e acima.
Doctor Jones
qual seria a consequência? se alguém omitir o transactionScope.Complete () no bloco mencionado acima? Você acha que eu deveria fazer outra pergunta para isso?
Eakan Gopalakrishnan
@EakanGopalakrishnan Deixar de chamar esse método interrompe a transação, porque o gerente da transação interpreta isso como uma falha do sistema ou como exceções lançadas no escopo da transação. (Extraído do MSDN msdn.microsoft.com/en-us/library/… )
Doctor Jones
1
@JsonStatham foi adicionado nesta solicitação de recebimento , que é para o marco 2.1.0
Doutor Jones
83

Os métodos de extensão podem facilitar isso

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
fonte
Usar isso no meu projeto faz com que o pool de conexões seja completamente utilizado, resultando em exceção. não consigo descobrir o porquê. Mais alguém com esses problemas? Alguma sugestão?
precisa saber é o seguinte
1
Sem problemas Ben, não se esqueça de descartar SEMPRE o seu contexto de conexão.
Alexandre
Conseguiu diminuir o problema para excluir o escopo da transação como uma possível causa. Obrigado. Teve algo a ver com algumas tentativas de conexão que eu tinha no meu construtor.
Ben Tidman
Eu acredito que o escopo deve ser TransactionScopeOption.Suppress
CodeGrue
@Alexandre O que aconteceria se eu fizesse isso em outra transação ReadCommitted? Por exemplo, gerou uma transação para começar a salvar dados, mas agora estou consultando mais dados e, portanto, gerando uma transação ReadUncommitted dentro? Chamar isso de "Completo" também completará minha transação externa? Aconselhar gentilmente :)
Jason Loki Smith
27

Se você precisar de algo em geral, a melhor maneira que descobrimos ser menos invasiva do que realmente iniciar um escopo de transações a cada vez é simplesmente definir o nível de isolamento de transação padrão em sua conexão depois de criar o contexto do objeto executando este comando simples:

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

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

Com essa técnica, fomos capazes de criar um provedor EF simples que cria o contexto para nós e, na verdade, executa esse comando todas as vezes para todo o nosso contexto, de modo que estamos sempre na "leitura não confirmada" por padrão.

Frank.Germain
fonte
2
Definir o nível de isolamento da transação sozinho não terá nenhum efeito. Você realmente precisa estar executando uma transação para que ela tenha algum efeito. A documentação do MSDN para READ UNCOMMITTED afirma Transactions running at the READ UNCOMMITTED level do not issue shared locks. Isso implica que você deve estar executando dentro de uma transação para obter o benefício. (extraído de msdn.microsoft.com/en-gb/library/ms173763.aspx ). Sua abordagem pode ser menos intrusiva, mas não conseguirá nada se você não usar uma transação.
Doctor Jones
3
A documentação do MSDN diz: "Controla o comportamento de bloqueio e controle de versão de linha das instruções Transact-SQL emitidas por uma conexão com o SQL Server". e "Especifica que as instruções podem ler linhas que foram modificadas por outras transações, mas ainda não confirmadas". Esta declaração que escrevi afeta TODAS as instruções SQL, estando dentro de uma transação ou não. Não gosto de contradizer as pessoas on-line, mas você está claramente errado com base no uso dessa declaração em um grande ambiente de produção. Não assuma as coisas, tente!
Frank.Germain
Eu tentei, temos um ambiente de alta carga em que a não execução de consultas em um desses escopos de transação (e uma transação correspondente) resultará em um impasse. Minhas observações foram feitas em um servidor SQL 2005, então não sei se o comportamento mudou desde então. Eu, portanto, recomendo isso; se você especificar um nível de isolamento não confirmado de leitura, mas continuar enfrentando conflitos, tente colocar suas consultas em uma transação. Se você não tiver conflitos sem criar uma transação, será justo.
Doctor Jones
3
@DoctorJones - com relação ao Microsoft SQL Server, todas as consultas são inerentemente transações. A especificação de uma transação explícita é apenas um meio de agrupar 2 ou mais instruções na mesma transação, para que possam ser consideradas uma unidade atômica de trabalho. O SET TRANSACTION ISOLATION LEVEL...comando afeta uma propriedade no nível da conexão e, portanto, afeta todas as instruções SQL feitas desse ponto em diante (para essa conexão), a menos que sejam substituídas por uma dica de consulta. Esse comportamento existe desde pelo menos o SQL Server 2000 e provavelmente antes.
Solomon Rutzky
5
@DoctorJones - Confira: msdn.microsoft.com/en-us/library/ms173763.aspx . Aqui está um teste. Em SSMS, abrir uma consulta (# 1) e execute: CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Abra outra consulta (# 2) e execute: SELECT * FROM ##Test;. O SELECT não retornará, pois está sendo bloqueado pela transação ainda aberta na guia nº 1, que está usando um bloqueio exclusivo. Cancele o SELECT no # 2. Execute SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDuma vez na guia nº 2. Execute apenas o SELECT novamente na guia nº 2 e ele voltará. Certifique-se de executar ROLLBACKna guia nº 1.
Solomon Rutzky
21

Embora eu tenha concordado absolutamente que usar o nível de isolamento de transação Read Uncommitted é a melhor opção, mas algum tempo você forçou a usar a dica NOLOCK por solicitação do gerente ou cliente e nenhuma razão contra isso foi aceita.

Com o Entity Framework 6, você pode implementar o próprio DbCommandInterceptor assim:

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)");
        }
    }
}

Com esta classe, você pode aplicá-la no início do aplicativo:

DbInterception.Add(new NoLockInterceptor());

E desabilite condicionalmente a adição de NOLOCKdica nas consultas do encadeamento atual:

NoLockInterceptor.SuppressNoLock = true;
Yuriy Rozhovetskiy
fonte
Eu gosto desta solução, embora tenha alterado ligeiramente o regex para:
Russ
2
para evitar a adição de nolock a tabela derivada que faz com que um erro :) (<tableAlias>] AS [Extensão \ d +] (?! COM (NOLOCK))?).
Russ
Definir SuppressNoLock no nível do encadeamento é uma maneira conveniente, mas é fácil esquecer de desmarcar o booleano; você deve usar uma função que retorne IDisposable; o método Dispose pode simplesmente definir o bool como false novamente. Além disso, o ThreadStatic não é realmente compatível com async / waitit: stackoverflow.com/questions/13010563/…
Jaap
Ou, se você preferir usar NÍVEL DE ISOLAMENTO: 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
Ele também anexa o nolock às funções do banco de dados. Como evitar funções?
Ivan Lewis
9

Aprimorar a resposta aceita pelo doutor Jones e usar o PostSharp ;

Primeiro " 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();
            }
        }
    }
}

Então, sempre que você precisar,

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

        }
    }

Ser capaz de adicionar "NOLOCK" a um interceptador também é bom, mas não funciona ao conectar-se a outros sistemas de banco de dados como o Oracle.

myuce
fonte
6

Para contornar isso, crio uma visualização no banco de dados e aplico NOLOCK na consulta da visualização. Trato então a visualização como uma tabela na EF.

Ryan Galloway
fonte
4

Com a introdução do EF6, a Microsoft recomenda o uso do método BeginTransaction ().

Você pode usar BeginTransaction em vez de TransactionScope no EF6 + e 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
}
Todos
fonte
2

Não, na verdade - o Entity Framework é basicamente uma camada bastante rígida acima do banco de dados real. Suas consultas são formuladas no ESQL - SQL de entidade - que é primeiramente direcionado ao seu modelo de entidade e, como o EF suporta vários back-ends de banco de dados, você não pode realmente enviar SQL "nativo" diretamente para seu back-end.

A dica de consulta NOLOCK é uma coisa específica do SQL Server e não funcionará em nenhum dos outros bancos de dados suportados (a menos que eles também tenham implementado a mesma dica - o que duvido muito).

Marc

marc_s
fonte
Esta resposta está desatualizada - você pode usar o NOLOCK como já mencionado, e pode executar SQL "nativo" usando Database.ExecuteSqlCommand()ou DbSet<T>.SqlQuery().
Dunc
1
@ Dunc: obrigado pelo voto negativo - btw: você NÃO deve usar (NOLOCK)mesmo assim - veja Bad Habits to kick - colocando NOLOCK em todos os lugares - NÃO É RECOMENDADO usar isso em todos os lugares - muito pelo contrário!
22416 Marc
0

Uma opção é usar um procedimento armazenado (semelhante à solução de visualização proposta por Ryan) e, em seguida, executar o procedimento armazenado no EF. Dessa forma, o procedimento armazenado executa a leitura suja enquanto o EF apenas canaliza os resultados.

Rafiki
fonte