Kerangka Entitas dengan NOLOCK

138

Bagaimana saya bisa menggunakan NOLOCKfungsi pada Entity Framework? Apakah XML satu-satunya cara untuk melakukan ini?

OneSmartGuy
sumber

Jawaban:

207

Tidak, tetapi Anda dapat memulai transaksi dan mengatur level isolasi untuk membaca tanpa komitmen . Ini pada dasarnya melakukan hal yang sama dengan NOLOCK, tetapi alih-alih melakukannya berdasarkan tabel, itu akan melakukannya untuk semua yang ada dalam ruang lingkup transaksi.

Jika itu terdengar seperti yang Anda inginkan, inilah cara Anda dapat melakukannya ...

//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();
}
Dokter Jones
sumber
Excellent @DoctaJonez Apakah ada yang baru diperkenalkan di EF4 untuk ini?
FMFF
@FMFF Saya tidak tahu apakah ada yang baru diperkenalkan untuk EF4. Saya tahu bahwa kode di atas berfungsi dengan EFv1 dan di atas sekalipun.
Dokter Jones
apa akibatnya? jika seseorang menghilangkan transactionScope.Complete () di blok yang disebutkan di atas? Apakah Anda pikir saya harus mengajukan pertanyaan lain untuk ini?
Eakan Gopalakrishnan
@EakanGopalakrishnan Gagal menyebut metode ini membatalkan transaksi, karena manajer transaksi mengartikan ini sebagai kegagalan sistem, atau pengecualian yang dimasukkan dalam ruang lingkup transaksi. (Diambil dari MSDN msdn.microsoft.com/en-us/library/… )
Doctor Jones
1
@JsonStatham telah ditambahkan dalam permintaan tarik ini , yaitu untuk tonggak pencapaian 2.1.0
Doctor Jones
83

Metode penyuluhan dapat membuat ini lebih mudah

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
sumber
Dengan menggunakan ini di proyek saya, hasil koneksi pool sepenuhnya digunakan menghasilkan pengecualian. tidak tahu mengapa. Adakah orang lain yang mengalami masalah ini? Ada saran?
Ben Tidman
1
Tidak ada masalah Ben, jangan lupa untuk SELALU membuang konteks koneksi Anda.
Alexandre
Mampu mempersempit masalah untuk mengecualikan ruang lingkup transaksi sebagai kemungkinan penyebabnya. Terima kasih. Ada hubungannya dengan beberapa hal coba lagi koneksi yang saya miliki di konstruktor saya.
Ben Tidman
Saya percaya ruang lingkup harus TransactionScopeOption.Suppress
CodeGrue
@Alexandre Apa yang akan terjadi jika saya melakukan ini dalam Transaksi ReadCommitted lain? Misalnya saya memunculkan transaksi untuk mulai menyimpan data tetapi sekarang saya meminta lebih banyak data dan karenanya memunculkan Transaksi ReadUncommitted di dalam? Akankah memanggil "Lengkap" ini juga menyelesaikan transaksi luar saya? Mohon saran :)
Jason Loki Smith
27

Jika Anda memerlukan sesuatu yang luas, cara terbaik yang kami temukan yang tidak terlalu mengganggu daripada benar-benar memulai transaksi setiap kali, adalah dengan hanya mengatur tingkat isolasi transaksi default pada koneksi Anda setelah Anda membuat konteks objek Anda dengan menjalankan perintah sederhana ini:

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

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

Dengan teknik ini, kami dapat membuat penyedia EF sederhana yang menciptakan konteks untuk kami dan benar-benar menjalankan perintah ini setiap kali untuk semua konteks kami sehingga kami selalu "membaca tanpa komitmen" secara default.

Frank. Jerman
sumber
2
Pengaturan level isolasi transaksi saja tidak akan berpengaruh. Anda benar-benar harus berjalan dalam transaksi agar dapat memiliki efek. Dokumentasi MSDN untuk status READ UNCOMMITTED Transactions running at the READ UNCOMMITTED level do not issue shared locks. Ini menyiratkan bahwa Anda harus menjalankan transaksi untuk mendapatkan manfaat. (diambil dari msdn.microsoft.com/en-gb/library/ms173763.aspx ). Pendekatan Anda mungkin tidak terlalu mengganggu, tetapi tidak akan mencapai apa pun jika Anda tidak menggunakan transaksi.
Dokter Jones
3
Dokumentasi MSDN mengatakan: "Mengontrol penguncian dan perilaku versi baris pernyataan Transact-SQL yang dikeluarkan oleh koneksi ke SQL Server." dan "Menentukan bahwa laporan dapat membaca baris yang telah dimodifikasi oleh transaksi lain tetapi belum dilakukan." Pernyataan yang saya tulis ini memengaruhi SETIAP pernyataan SQL, apakah ada di dalam transaksi atau tidak. Saya tidak suka mengkontradiksi orang secara online tetapi Anda jelas salah dalam hal itu berdasarkan penggunaan pernyataan ini oleh kami dalam lingkungan produksi yang besar. Jangan berasumsi, COBA MEREKA!
Frank. Jerman
Saya telah mencobanya, kami memiliki lingkungan beban tinggi di mana tidak melakukan kueri dalam salah satu cakupan transaksi ini (dan transaksi yang cocok) akan mengakibatkan kebuntuan. Pengamatan saya dilakukan pada server SQL 2005, jadi saya tidak tahu apakah perilaku telah berubah sejak itu. Karena itu saya akan merekomendasikan ini; jika Anda menentukan tingkat isolasi tanpa komitmen yang sudah dibaca tetapi terus mengalami kebuntuan, coba masukkan pertanyaan Anda dalam transaksi. Jika Anda tidak mengalami kebuntuan tanpa membuat transaksi, maka cukup adil.
Dokter Jones
3
@DoctorJones - berkenaan dengan Microsoft SQL Server, semua permintaan secara inheren transaksi. Menentukan transaksi eksplisit hanyalah alat untuk mengelompokkan 2 atau lebih pernyataan ke dalam transaksi yang sama sehingga mereka dapat dianggap sebagai unit kerja atom. The SET TRANSACTION ISOLATION LEVEL...perintah mempengaruhi properti koneksi-tingkat dan karenanya mempengaruhi semua pernyataan SQL yang dibuat dari saat itu (untuk koneksi YANG), kecuali diganti dengan petunjuk query. Perilaku ini telah ada sejak setidaknya SQL Server 2000, dan kemungkinan sebelumnya.
Solomon Rutzky
5
@DoctorJones - Lihat: msdn.microsoft.com/en-us/library/ms173763.aspx . Ini tesnya. Dalam SSMS, membuka query (# 1) dan menjalankan: CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Buka permintaan lain (# 2) dan menjalankan: SELECT * FROM ##Test;. SELECT tidak akan kembali karena diblokir oleh transaksi yang masih terbuka di tab # 1 yang menggunakan kunci eksklusif. Batalkan SELECT di # 2. Jalankan SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDsekali di tab # 2. Jalankan SELECT lagi di tab # 2 dan itu akan kembali. Pastikan dijalankan ROLLBACKdi tab # 1.
Solomon Rutzky
21

Meskipun saya benar-benar setuju bahwa menggunakan tingkat isolasi transaksi Read Uncommitted adalah pilihan terbaik, tetapi beberapa kali Anda dipaksa untuk menggunakan petunjuk NOLOCK atas permintaan manajer atau klien dan tidak ada alasan untuk menentang hal ini diterima.

Dengan Entity Framework 6 Anda dapat menerapkan DbCommandInterceptor sendiri seperti ini:

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

Dengan kelas ini tersedia, Anda dapat menerapkannya pada awal aplikasi:

DbInterception.Add(new NoLockInterceptor());

Dan matikan menambahkan NOLOCKpetunjuk ke permintaan untuk thread saat ini:

NoLockInterceptor.SuppressNoLock = true;
Yuriy Rozhovetskiy
sumber
Saya suka solusi ini meskipun saya mengubah regex sedikit ke:
Russ
2
(? <tableAlias>] AS [Extent \ d +] (?! WITH (NOLOCK))) untuk mencegah penambahan nolock ke tabel turunan yang menyebabkan kesalahan. :)
Russ
Mengatur SuppressNoLock pada level thread adalah cara yang mudah, tetapi mudah untuk melupakan unset the boolean, Anda harus menggunakan fungsi yang mengembalikan IDisposable, metode Buang hanya dapat mengatur bool menjadi false lagi. Juga, ThreadStatic tidak benar-benar kompatibel dengan async / await: stackoverflow.com/questions/13010563/…
Jaap
Atau, jika Anda lebih suka menggunakan LEVEL ISOLASI: 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
Ini menambahkan nolock ke fungsi basis data juga. Bagaimana cara menghindari fungsi?
Ivan Lewis
9

Meningkatkan jawaban yang diterima Dokter Jones dan menggunakan PostSharp ;

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

Maka kapan pun Anda membutuhkannya,

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

        }
    }

Mampu menambahkan "NOLOCK" dengan interceptor juga bagus tetapi tidak akan berfungsi saat menghubungkan ke sistem database lain seperti Oracle.

myuce
sumber
6

Untuk mengatasinya, saya membuat tampilan pada database dan menerapkan NOLOCK pada permintaan tampilan. Saya kemudian memperlakukan tampilan sebagai tabel dalam EF.

Ryan Galloway
sumber
4

Dengan diperkenalkannya EF6, Microsoft merekomendasikan untuk menggunakan metode BeginTransaction ().

Anda dapat menggunakan BeginTransaction alih-alih TransactionScope di EF6 + dan 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
sumber
2

Tidak, tidak juga - Kerangka Entitas pada dasarnya adalah lapisan yang cukup ketat di atas basis data Anda yang sebenarnya. Kueri Anda diformulasikan dalam ESQL - Entity SQL - yang pertama-tama ditargetkan untuk model entitas Anda, dan karena EF mendukung banyak database backend, Anda tidak dapat benar-benar mengirim SQL "asli" langsung ke backend Anda.

Petunjuk kueri NOLOCK adalah hal khusus SQL Server dan tidak akan berfungsi pada basis data yang didukung lainnya (kecuali jika mereka juga menerapkan petunjuk yang sama - yang sangat saya ragukan).

Marc

marc_s
sumber
Jawaban ini kedaluwarsa - Anda dapat menggunakan NOLOCK seperti yang disebutkan orang lain, dan Anda dapat menjalankan SQL "asli" menggunakan Database.ExecuteSqlCommand()atau DbSet<T>.SqlQuery().
Dunc
1
@Dunc: terima kasih untuk downvote - btw: Anda TIDAK boleh menggunakan (NOLOCK)- lihat Kebiasaan Buruk untuk menendang - menempatkan NOLOCK di mana - mana - TIDAK DIREKOMENDASIKAN untuk menggunakan ini di mana-mana - justru sebaliknya!
marc_s
0

Salah satu opsi adalah menggunakan prosedur tersimpan (mirip dengan solusi tampilan yang diusulkan oleh Ryan) dan kemudian menjalankan prosedur tersimpan dari EF. Dengan cara ini prosedur yang tersimpan melakukan pembacaan kotor sementara EF hanya menyalurkan hasilnya.

Rafiki
sumber