Entity Framework ด้วย NOLOCK


138

ฉันจะใช้NOLOCKฟังก์ชันบน Entity Framework ได้อย่างไร XML เป็นวิธีเดียวที่จะทำสิ่งนี้หรือไม่?

คำตอบ:


207

ไม่มี แต่คุณสามารถเริ่มต้นการทำธุรกรรมและการตั้งค่าระดับการแยกการอ่าน uncommited สิ่งนี้จะทำเช่นเดียวกับ NOLOCK แต่แทนที่จะทำตามตารางต่อมันจะทำทุกอย่างภายในขอบเขตของการทำธุรกรรม

หากดูเหมือนว่าสิ่งที่คุณต้องการนี่คือวิธีที่คุณสามารถทำมันได้ ...

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

ยอดเยี่ยม @DoctaJonez มีอะไรใหม่ที่แนะนำใน EF4 สำหรับเรื่องนี้หรือไม่?
FMFF

@FMFF ฉันไม่รู้ว่ามีการแนะนำสิ่งใหม่สำหรับ EF4 หรือไม่ ฉันรู้ว่ารหัสข้างต้นทำงานร่วมกับ EFv1 ขึ้นไปว่า
หมอโจนส์

สิ่งที่จะเป็นผลที่ตามมา? ถ้ามีคนละเว้น transactionScope.Complete () ในบล็อกที่กล่าวถึงข้างต้น คุณคิดว่าฉันควรยื่นคำถามอีกข้อนี้หรือไม่?
Eakan Gopalakrishnan

@EakanGopalakrishnan ไม่สามารถเรียกวิธีการนี้ยกเลิกธุรกรรมได้เนื่องจากผู้จัดการธุรกรรมตีความว่านี่เป็นความล้มเหลวของระบบหรือมีข้อยกเว้นเกิดขึ้นภายในขอบเขตของธุรกรรม (นำมาจาก MSDN msdn.microsoft.com/en-us/library/… )
Doctor Jones

1
@JsonStatham มันได้รับการเพิ่มในคำขอดึงนี้ซึ่งเป็นเหตุการณ์สำคัญ 2.1.0
หมอโจนส์

83

วิธีการขยายสามารถทำให้ง่ายขึ้น

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

การใช้สิ่งนี้ในโครงการของฉันส่งผลให้พูลการเชื่อมต่อถูกใช้อย่างสมบูรณ์ทำให้เกิดข้อยกเว้น ไม่สามารถหาสาเหตุได้ มีคนอื่นที่มีปัญหานี้หรือไม่ ข้อเสนอแนะใด ๆ
Ben Tidman

1
ไม่มีปัญหา Ben อย่าลืมทิ้งบริบทการเชื่อมต่อของคุณไว้เสมอ
Alexandre

สามารถทำให้ปัญหาแคบลงเพื่อแยกขอบเขตการทำธุรกรรมออกเป็นสาเหตุที่เป็นไปได้ ขอบคุณ มีบางอย่างที่เกี่ยวข้องกับการลองเชื่อมต่อใหม่ที่ฉันมีในตัวสร้างของฉัน
Ben Tidman

ฉันเชื่อว่าขอบเขตควรเป็น TransactionScopeOption.Suppress
CodeGrue

@Alexandre จะเกิดอะไรขึ้นถ้าฉันทำสิ่งนี้ภายในธุรกรรม ReadCommitted อื่น ตัวอย่างเช่นฉันวางไข่ธุรกรรมเพื่อเริ่มการบันทึกข้อมูล แต่ตอนนี้ฉันกำลังสอบถามข้อมูลเพิ่มเติม การเรียก "เสร็จสมบูรณ์" นี้จะทำธุรกรรมนอกของฉันด้วยหรือไม่ ขอคำแนะนำ :)
Jason Loki Smith

27

หากคุณต้องการสิ่งที่มีขนาดใหญ่วิธีที่ดีที่สุดที่เราพบว่าน้อยกว่าการเริ่มต้นสโคปธุรกรรมในแต่ละครั้งคือการตั้งค่าระดับการแยกธุรกรรมเริ่มต้นในการเชื่อมต่อของคุณหลังจากที่คุณสร้างบริบทวัตถุโดยใช้คำสั่งง่าย ๆ นี้

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

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

ด้วยเทคนิคนี้เราสามารถสร้างผู้ให้บริการ EF อย่างง่ายที่สร้างบริบทให้เราและเรียกใช้คำสั่งนี้ทุกครั้งสำหรับบริบททั้งหมดของเราเพื่อให้เราอยู่ใน "read uncommitted" โดยค่าเริ่มต้น


2
การตั้งค่าระดับการแยกธุรกรรมเพียงอย่างเดียวจะไม่มีผลกระทบใด ๆ คุณต้องทำงานภายในธุรกรรมเพื่อให้มีผลกระทบใด ๆ เอกสาร MSDN Transactions running at the READ UNCOMMITTED level do not issue shared locksสำหรับอ่านปราศจากข้อผูกมัดรัฐ หมายความว่าคุณต้องทำงานภายในธุรกรรมเพื่อรับผลประโยชน์ (นำมาจากmsdn.microsoft.com/en-gb/library/ms173763.aspx ) วิธีการของคุณอาจล่วงล้ำน้อยลง แต่จะไม่ประสบความสำเร็จหากคุณไม่ใช้ธุรกรรม
หมอโจนส์

3
เอกสาร MSDN พูดว่า: "ควบคุมพฤติกรรมการล็อกและการควบคุมเวอร์ชันแถวของคำสั่ง Transact-SQL ที่ออกให้โดยการเชื่อมต่อกับ SQL Server" และ "ระบุว่าคำสั่งสามารถอ่านแถวที่ได้รับการแก้ไขโดยธุรกรรมอื่น ๆ แต่ยังไม่ได้ยืนยัน" คำแถลงที่ฉันเขียนนี้ส่งผลกระทบต่อคำสั่ง SQL ทุกคำว่าอยู่ในธุรกรรมหรือไม่ ฉันไม่ชอบที่จะคัดค้านผู้คนทางออนไลน์ แต่คุณเห็นได้ชัดว่าคน ๆ นั้นผิดอยู่บนพื้นฐานของการใช้คำสั่งนี้ในสภาพแวดล้อมการผลิตขนาดใหญ่ อย่าคิดเลยลองพวกเขาดู!
Frank.Germain

ฉันลองพวกเขาแล้วเรามีสภาพแวดล้อมในการโหลดสูงที่ไม่ทำการค้นหาภายในขอบเขตการทำธุรกรรมเหล่านี้ (และธุรกรรมที่ตรงกัน) จะส่งผลให้เกิดการหยุดชะงัก ข้อสังเกตของฉันถูกสร้างขึ้นบนเซิร์ฟเวอร์ SQL 2005 ดังนั้นฉันไม่ทราบว่าพฤติกรรมนั้นเปลี่ยนไปหรือไม่ ฉันจะแนะนำสิ่งนี้; หากคุณระบุระดับการแยกที่ไม่ได้อ่าน แต่ยังคงพบกับ deadlock ให้ลองใส่คำสั่งของคุณในการทำธุรกรรม หากคุณไม่ประสบกับการหยุดชะงักโดยไม่ต้องสร้างธุรกรรมก็พอแล้ว
หมอโจนส์

3
@DoctorJones - เกี่ยวกับ Microsoft SQL Server การสืบค้นทั้งหมดเป็นการทำธุรกรรมโดยเนื้อแท้ การระบุธุรกรรมที่ชัดเจนเป็นเพียงวิธีการจัดกลุ่มคำสั่ง 2 รายการขึ้นไปในธุรกรรมเดียวกันเพื่อให้พวกเขาสามารถพิจารณาได้ว่าเป็นหน่วยงานปรมาณู SET TRANSACTION ISOLATION LEVEL...คำสั่งมีผลต่อคุณสมบัติการเชื่อมต่อระดับและด้วยเหตุนี้ส่งผลกระทบต่อคำสั่ง SQL ทั้งหมดที่ทำจากจุดนั้นเป็นต้นไป (สำหรับการเชื่อมต่อนั้น) เว้นแต่แทนที่โดยคำใบ้แบบสอบถาม พฤติกรรมนี้มีมาตั้งแต่อย่างน้อย SQL Server 2000 และเป็นไปได้ก่อน
โซโลมอน Rutzky

5
@DoctorJones - เช็คเอาต์: msdn.microsoft.com/en-us/library/ms173763.aspx นี่คือการทดสอบ ใน SSMS เปิดแบบสอบถาม (# 1) CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);และเรียกใช้: เปิดการสอบถามอื่น (# 2) SELECT * FROM ##Test;และการทำงาน: SELECT จะไม่ส่งคืนเนื่องจากถูกบล็อกโดยธุรกรรมที่เปิดอยู่ในแท็บ # 1 ที่ใช้การล็อกแบบเอกสิทธิ์ ยกเลิกการเลือกใน # 2 ทำงานSET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDครั้งเดียวในแท็บ # 2 เรียกใช้เพียงเลือกอีกครั้งในแท็บ # 2 และมันจะกลับมา อย่าลืมรันROLLBACKในแท็บ # 1
โซโลมอน Rutzky

21

แม้ว่าฉันจะเห็นด้วยอย่างแน่นอนว่าการใช้ระดับการแยกธุรกรรมแบบไม่มีข้อผูกมัดเป็นทางเลือกที่ดีที่สุด แต่บางครั้งคุณถูกบังคับให้ใช้คำใบ้ NOLOCK ตามคำขอของผู้จัดการหรือลูกค้าและไม่มีเหตุผลใดที่ยอมรับได้

ด้วย Entity Framework 6 คุณสามารถใช้ DbCommandInterceptor ของตัวเองเช่นนี้:

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

เมื่อใช้คลาสนี้คุณสามารถสมัครได้ตั้งแต่เริ่มต้นแอปพลิเคชัน:

DbInterception.Add(new NoLockInterceptor());

และปิดการเพิ่มNOLOCKคำใบ้ลงในแบบสอบถามแบบมีเงื่อนไขสำหรับเธรดปัจจุบัน:

NoLockInterceptor.SuppressNoLock = true;

ฉันชอบวิธีนี้แม้ว่าฉันจะเปลี่ยน regex เล็กน้อยเป็น:
Russ

2
เพื่อป้องกันไม่ให้เพิ่ม NOLOCK ไปที่โต๊ะที่ได้มาซึ่งทำให้เกิดข้อผิดพลาด :) (<tableAlias>] AS [ขอบเขต \ d +] (?! กับ (NOLOCK))?).
รัส

การตั้งค่า SuppressNoLock ที่ระดับเธรดเป็นวิธีที่สะดวก แต่ก็ง่ายที่จะลืมการตั้งค่าบูลีนคุณควรใช้ฟังก์ชั่นที่ส่งกลับ IDisposable วิธีการกำจัดสามารถตั้งค่าบูลเป็นเท็จอีกครั้ง นอกจากนี้ ThreadStatic ยังเข้ากันไม่ได้กับ async / await: stackoverflow.com/questions/13010563//
Jaap

หรือหากคุณต้องการใช้ระดับการแยก: 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

มันเป็นการต่อท้าย nolock กับฟังก์ชั่นฐานข้อมูลด้วย จะหลีกเลี่ยงฟังก์ชั่นได้อย่างไร?
Ivan Lewis เมื่อ

9

เสริมสร้างบนหมอโจนส์ตอบรับ 's และการใช้PostSharp ;

ครั้งแรก " 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();
            }
        }
    }
}

จากนั้นเมื่อใดก็ตามที่คุณต้องการ

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

        }
    }

การสามารถเพิ่ม "NOLOCK" ด้วย interceptor ก็ดี แต่จะไม่ทำงานเมื่อเชื่อมต่อกับระบบฐานข้อมูลอื่นเช่น Oracle เช่นนี้


6

รับรอบนี้ฉันสร้างมุมมองในฐานข้อมูลและใช้ NOLOCK ในแบบสอบถามของมุมมอง ฉันจึงมองว่าเป็นตารางภายใน EF


4

ด้วยการแนะนำของ EF6 นั้น Microsoft แนะนำให้ใช้วิธี BeginTransaction ()

คุณสามารถใช้ BeginTransaction แทน TransactionScope ใน EF6 + และ 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

ไม่ไม่จริง - Entity Framework นั้นเป็นชั้นที่ค่อนข้างเข้มงวดเหนือฐานข้อมูลจริง ข้อความค้นหาของคุณถูกกำหนดใน ESQL - เอนทิตี SQL - ซึ่งเป็นเป้าหมายแรกที่มีต่อโมเดลเอนทิตีของคุณและเนื่องจาก EF รองรับแบ็กเอนด์ฐานข้อมูลหลายตัวคุณไม่สามารถส่ง SQL "ดั้งเดิม" ไปยังแบ็กเอนด์ของคุณได้โดยตรง

คำแนะนำการสืบค้น NOLOCK เป็นสิ่งเฉพาะของ SQL Server และจะไม่ทำงานกับฐานข้อมูลอื่น ๆ ที่รองรับ (เว้นแต่ว่าพวกเขาจะใช้คำใบ้เดียวกัน - ซึ่งฉันสงสัยอย่างมาก)

มาร์ค


คำตอบนี้จะออกจากวัน - คุณสามารถใช้ NOLOCK กับคนอื่น ๆ ได้กล่าวและคุณสามารถดำเนินการ "แม่" SQL ใช้หรือDatabase.ExecuteSqlCommand() DbSet<T>.SqlQuery()
Dunc

1
@Dunc: ขอบคุณสำหรับ downvote - btw: คุณไม่ควรใช้(NOLOCK)ต่อไป - ดูBad Habits to kick - วาง NOLOCK ทุกที่ - ไม่แนะนำให้ใช้มันในทุกที่ - ค่อนข้างตรงกันข้าม!
marc_s

0

ทางเลือกหนึ่งคือการใช้กระบวนงานที่เก็บไว้ (คล้ายกับโซลูชันมุมมองที่เสนอโดย Ryan) จากนั้นดำเนินการขั้นตอนการจัดเก็บจาก EF วิธีนี้ขั้นตอนการจัดเก็บจะดำเนินการอ่านสกปรกในขณะที่ EF เพียงส่งผลลัพธ์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.