Khung thực thể với NOLOCK


138

Làm cách nào tôi có thể sử dụng NOLOCKchức năng trên Entity Framework? Có phải XML là cách duy nhất để làm điều này?

Câu trả lời:


207

Không, nhưng bạn có thể bắt đầu một giao dịch và đặt mức cô lập để đọc không được cam kết . Điều này về cơ bản thực hiện giống như NOLOCK, nhưng thay vì thực hiện trên cơ sở mỗi bảng, nó sẽ thực hiện mọi thứ trong phạm vi giao dịch.

Nếu điều đó nghe giống như những gì bạn muốn, thì đây là cách bạn có thể thực hiện ...

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

Tuyệt vời @DoctaJonez Có bất cứ điều gì mới được giới thiệu trong EF4 cho điều này?
FMFF

@FMFF Tôi không biết nếu có bất cứ điều gì mới được giới thiệu cho EF4. Tôi biết rằng đoạn mã trên hoạt động với EFv1 trở lên.
Bác sĩ Jones

hậu quả sẽ ra sao? nếu ai đó bỏ qua giao dịchScope.Complete () trong khối được đề cập ở trên? Bạn có nghĩ rằng tôi nên nộp một câu hỏi khác cho điều này?
Eakan Gopalakrish Nam

@EakanGopalakrishnan Không thể gọi phương thức này hủy bỏ giao dịch, bởi vì người quản lý giao dịch hiểu đây là lỗi hệ thống hoặc ngoại lệ được ném trong phạm vi giao dịch. (Lấy từ MSDN msdn.microsoft.com/en-us/l Library / từ )
Bác sĩ Jones

1
@JsonStatham nó đã được thêm vào trong yêu cầu kéo này , đó là cho cột mốc 2.1.0
Doctor Jones

83

Phương pháp mở rộng có thể làm cho điều này dễ dàng hơn

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

Sử dụng điều này trong dự án của tôi dẫn đến kết nối nhóm được sử dụng hoàn toàn dẫn đến ngoại lệ. không thể hiểu tại sao. Bất kỳ ai khác có vấn đề này? Bất kỳ đề xuất?
Ben Tidman

1
Không có vấn đề gì Ben, đừng quên LUÔN LUÔN xử lý bối cảnh kết nối của bạn.
Alexandre

Đã có thể thu hẹp vấn đề để loại trừ phạm vi giao dịch là nguyên nhân có thể. Cảm ơn. Có một cái gì đó để làm với một số công cụ thử lại kết nối tôi có trong nhà xây dựng của tôi.
Ben Tidman

Tôi tin rằng phạm vi nên là TransactionScopeOption.Suppress
CodeGrue

@Alexandre Điều gì sẽ xảy ra nếu tôi thực hiện việc này trong một Giao dịch đã đọc khác? Chẳng hạn, tôi đã sinh ra một giao dịch để bắt đầu lưu dữ liệu nhưng bây giờ tôi đang truy vấn nhiều dữ liệu hơn và do đó sinh ra một Giao dịch ReadUncommned trong? Sẽ gọi đây là "Hoàn thành" cũng hoàn thành giao dịch bên ngoài của tôi? Vui lòng tư vấn :)
Jason Loki Smith

27

Nếu bạn cần một cái gì đó lớn, cách tốt nhất mà chúng tôi thấy ít xâm phạm hơn so với thực tế bắt đầu một giao dịch mỗi lần, là chỉ cần đặt mức cách ly giao dịch mặc định trên kết nối của bạn sau khi bạn đã tạo bối cảnh đối tượng của mình bằng cách chạy lệnh đơn giản này:

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

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

Với kỹ thuật này, chúng tôi đã có thể tạo một nhà cung cấp EF đơn giản tạo bối cảnh cho chúng tôi và thực sự chạy lệnh này mỗi lần cho tất cả bối cảnh của chúng tôi để chúng tôi luôn luôn "đọc không cam kết" theo mặc định.


2
Chỉ thiết lập mức cô lập giao dịch sẽ không có hiệu lực. Bạn thực sự cần phải được chạy trong một giao dịch để nó có hiệu lực. Tài liệu MSDN cho các trạng thái ĐỌC HIỂU Transactions running at the READ UNCOMMITTED level do not issue shared locks. Điều này ngụ ý rằng bạn phải chạy trong một giao dịch để nhận được lợi ích. (lấy từ msdn.microsoft.com/en-gb/l Library / ms173763.aspx ). Cách tiếp cận của bạn có thể ít xâm phạm hơn, nhưng nó sẽ không đạt được bất cứ điều gì nếu bạn không sử dụng giao dịch.
Bác sĩ Jones

3
Tài liệu MSDN cho biết: "Điều khiển hành vi phiên bản khóa và hàng của các câu lệnh Transact-SQL được phát hành bởi một kết nối đến SQL Server." và "Chỉ định rằng các câu lệnh có thể đọc các hàng đã được sửa đổi bởi các giao dịch khác nhưng chưa được cam kết." Câu lệnh này tôi đã viết có ảnh hưởng đến MỌI câu lệnh SQL, nó có nằm trong giao dịch hay không. Tôi không muốn mâu thuẫn với mọi người trên mạng nhưng rõ ràng bạn đã sai về điều đó dựa trên việc chúng tôi sử dụng tuyên bố này trong một môi trường sản xuất lớn. Đừng giả sử mọi thứ, HÃY THỬ!
Frank.Germain

Tôi đã thử chúng, chúng tôi đã có một môi trường tải cao, nơi không thực hiện các truy vấn trong một trong các phạm vi giao dịch này (và một giao dịch phù hợp) sẽ dẫn đến bế tắc. Các quan sát của tôi đã được thực hiện trên máy chủ SQL 2005, vì vậy tôi không biết liệu hành vi đó có thay đổi hay không. Do đó tôi muốn giới thiệu điều này; nếu bạn chỉ định mức cô lập không được cam kết đọc nhưng tiếp tục gặp bế tắc, hãy thử đặt truy vấn của bạn trong một giao dịch. Nếu bạn không gặp phải bế tắc mà không tạo giao dịch, thì đủ công bằng.
Bác sĩ Jones

3
@DoctorJones - liên quan đến Microsoft SQL Server, tất cả các truy vấn đều là giao dịch. Chỉ định một giao dịch rõ ràng chỉ là một phương tiện để nhóm 2 hoặc nhiều câu lệnh vào cùng một giao dịch để chúng có thể được coi là một đơn vị công việc nguyên tử. Các SET TRANSACTION ISOLATION LEVEL...lệnh ảnh hưởng đến một thuộc tính kết nối cấp và do đó ảnh hưởng đến tất cả các câu lệnh SQL được thực hiện từ thời điểm đó trở đi (đối với RẰNG kết nối), trừ khi ghi đè bởi một gợi ý truy vấn. Hành vi này đã xuất hiện từ ít nhất là SQL Server 2000 và có thể là trước đó.
Solomon Rutzky

5
@DoctorJones - Kiểm tra: msdn.microsoft.com/en-us/l Library / ms173763.aspx . Đây là một bài kiểm tra. Trong SSMS, mở truy vấn (# 1) và chạy : CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Mở một truy vấn khác (# 2) và chạy : SELECT * FROM ##Test;. CHỌN sẽ không quay trở lại vì nó đang bị chặn bởi giao dịch vẫn đang mở trong tab # 1 đang sử dụng khóa độc quyền. Hủy CHỌN trong # 2. Chạy SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDmột lần trong tab # 2. Chỉ chạy lại CHỌN trong tab # 2 và nó sẽ quay lại. Hãy chắc chắn để chạy ROLLBACKtrong tab # 1.
Solomon Rutzky

21

Mặc dù tôi hoàn toàn đồng ý rằng sử dụng cấp độ cách ly giao dịch Đọc không cam kết là lựa chọn tốt nhất, nhưng đôi khi bạn buộc phải sử dụng gợi ý NOLOCK theo yêu cầu của người quản lý hoặc khách hàng và không có lý do nào để chấp nhận điều này.

Với Entity Framework 6, bạn có thể triển khai DbCommandInterceptor của riêng mình như thế này:

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

Với lớp này tại chỗ, bạn có thể áp dụng nó khi bắt đầu ứng dụng:

DbInterception.Add(new NoLockInterceptor());

Và có điều kiện tắt thêm NOLOCKgợi ý vào các truy vấn cho chuỗi hiện tại:

NoLockInterceptor.SuppressNoLock = true;

Tôi thích giải pháp này mặc dù tôi đã thay đổi regex một chút thành:
Russ

2
(? <tableAlias>] NHƯ [Extent \ d +] (?! VỚI (NOLOCK))) để ngăn việc thêm nolock vào bảng dẫn xuất gây ra lỗi. :)
Russ

Đặt SuppressNoLock ở cấp độ luồng là một cách thuận tiện, nhưng thật dễ quên khi không đặt boolean, bạn nên sử dụng hàm trả về IDis Dùng một lần, phương thức Dispose chỉ có thể đặt lại bool thành false. Ngoài ra, ThreadStatic không thực sự tương thích với async / await: stackoverflow.com/questions/13010563/
mẹo

Hoặc, nếu bạn muốn sử dụng CẤP ĐỘ CẤP: 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

Nó đang nối thêm nolock vào các chức năng cơ sở dữ liệu. Làm thế nào để tránh cho các chức năng?
Ivan Lewis

9

Nâng cao câu trả lời được chấp nhận của Doctor Jones và sử dụng PostSharp ;

Đầu tiên " ReadUncommitedTransactionScopeAttribution "

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

Sau đó, bất cứ khi nào bạn cần nó,

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

        }
    }

Có thể thêm "NOLOCK" bằng một thiết bị chặn cũng tốt nhưng sẽ không hoạt động khi kết nối với các hệ thống cơ sở dữ liệu khác như Oracle.


6

Để hoàn thành điều này, tôi tạo một khung nhìn trên cơ sở dữ liệu và áp dụng NOLOCK cho truy vấn của khung nhìn. Sau đó, tôi coi khung nhìn như một bảng trong EF.


4

Với việc giới thiệu EF6, Microsoft khuyên bạn nên sử dụng phương thức BeginTransaction ().

Bạn có thể sử dụng BeginTransaction thay vì TransactionScope trong EF6 + và 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
}

2

Không, không thực sự - Entity Framework về cơ bản là một lớp khá nghiêm ngặt trên cơ sở dữ liệu thực tế của bạn. Các truy vấn của bạn được xây dựng trong ESQL - SQL thực thể - trước hết được nhắm mục tiêu theo mô hình thực thể của bạn và vì EF hỗ trợ nhiều phụ trợ cơ sở dữ liệu, bạn thực sự không thể gửi SQL "gốc" trực tiếp đến phụ trợ của mình.

Gợi ý truy vấn NOLOCK là một điều cụ thể của Máy chủ SQL và sẽ không hoạt động trên bất kỳ cơ sở dữ liệu được hỗ trợ nào khác (trừ khi chúng cũng đã thực hiện cùng một gợi ý - điều mà tôi rất nghi ngờ).

Marc


Câu trả lời này đã hết hạn - bạn có thể sử dụng NOLOCK như những người khác đã đề cập và bạn có thể thực thi SQL "gốc" bằng cách sử dụng Database.ExecuteSqlCommand()hoặc DbSet<T>.SqlQuery().
Dunc

1
@Dunc: cảm ơn vì downvote - btw: dù sao bạn cũng KHÔNG nên sử dụng (NOLOCK)- xem Thói quen xấu để đá - đặt NOLOCK ở mọi nơi - KHÔNG ĐƯỢC KHUYẾN NGHỊ để sử dụng điều này ở mọi nơi - hoàn toàn ngược lại!
marc_s

0

Một tùy chọn là sử dụng một thủ tục được lưu trữ (tương tự như giải pháp xem do Ryan đề xuất) và sau đó thực hiện thủ tục được lưu trữ từ EF. Bằng cách này, thủ tục được lưu trữ thực hiện việc đọc bẩn trong khi EF chỉ đưa ra kết quả.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.