2017-03-04 9 views
5

मैं sp_getapplock और सत्र स्वामी प्रकार के साथ ईएफ संदर्भ का उपयोग करने के जोखिम/सुरक्षा के बारे में सोच रहा हूं। docs के आधार पर, मेरी समझ यह है कि एक सत्र स्वामी का मतलब है कि सत्र समाप्त होने पर लॉक जारी किया जाएगा यदि इससे पहले स्पष्ट रूप से रिलीज़ नहीं होता है। मुझे लगता है कि यह भी मतलब है कि कनेक्शन समाप्त होता है।क्या EF6 DbContexts के साथ सत्र-स्वामित्व वाले SQL सर्वर sp_getapplock का उपयोग करना सुरक्षित है?

मेरे पास यह सब लेनदेन-स्वामित्व वाले ताले के साथ पहले से ही काम कर रहा है, लेकिन इससे अन्य मुद्दों और जटिलताओं को सामने लाया जाता है, इसलिए मैं सत्र-स्वामित्व वाले ताले के साथ ऐसा करने के बारे में सोच रहा हूं और व्यवहार के साथ कैसे टकराएगा या नहीं DbContext और इसके कनेक्शन हैंडलिंग।

मैं 100% सुनिश्चित नहीं हूं कि DbContext डिफ़ॉल्ट रूप से काम करता है, चाहे वह पूल का उपयोग करता हो या प्रत्येक संदर्भ उदाहरण खुलता है और अपने कनेक्शन बंद कर देता है (जो दस्तावेज़ मैंने पढ़ा है, वे बाद वाले सुझाव देते हैं)। इस सवाल के लिए मान लीजिए कि मैं कनेक्शन प्रबंधन (मैं शायद ही कभी करता हूं) के साथ कुछ भी नहीं कर रहा हूं, इसलिए ईएफ इसे संभालने में कामयाब रहा है, या जो कुछ भी इसे संभालता है उसे संभाला जा रहा है।

अगर मैं एक DbContext इंस्टेंस बनाएं कनेक्शन हड़पने, एक सत्र के स्वामित्व वाली ताला, संदर्भ के रूप में सामान्य, नहीं रिलीज ताला करते हैं और संदर्भ के निपटान बनाने के उपयोग करने के लिए एसक्यूएल निष्पादित, इस काम करेगा सही ढंग से? (वास्तविकता में, यह एक IDisposable आवरण है कि को रोकने के लिए होगा, लेकिन सवाल बनी हुई है।)

खराब उदाहरण देकर स्पष्ट करने के लिए:

using (var ctx = new MyContext()) { 
    var conn = ctx.Database.Connection.Open(); 
    conn.ExecuteSqlSomehow("sp_getapplock blahblah"); 
    try { 
     // Lots of queries, savechanges, etc. 
    } finally { 
     // Oops I forgot to conn.ExecuteSql("sp_release the lock"); 
    } 
} 

await WatchMovieAsync(); 

using (var ctx = new MyContext()) { 
    // Can this reuse the same connection, session and/or lock? 
} 

सवाल:

  1. एक और संदर्भ किसी भी तरह फिर से किया जा सका उदाहरण के निपटारे के बाद मेरे कनेक्शन और उसके सत्र लॉक का उपयोग करें?
  2. उस मामले के लिए, क्या कोई अन्य संदर्भ (उदाहरण के लिए किसी अन्य थ्रेड में) एक कनेक्शन का उपयोग कर सकता है जिस पर मैंने लॉक जारी किया था जबकि मूल धागा अभी भी using ब्लॉक में है?
  3. क्या ईएफ लॉक कनेक्शन बंद कर सकता है और बाद में उसी using ब्लॉक (उदा। SaveChanges के लिए) खोल सकता है, जिससे लॉक जारी किया जा सकता है?

उत्तर

0

उत्तर ऐसा नहीं लगता है। जाहिर है, मुख्य मुद्दा यह है कि जब भी पूल से एक पूल कनेक्शन का पुन: उपयोग किया जाता है (पूल में वापस नहीं छोड़ा जाता है) को किसी भी सत्र आधारित ताले को छोड़ना चाहिए, तो sp_reset_connection के लिए कॉल हैं। This social.msdn link का दावा है कि sp_reset_connection को ईएफ कमांड के बीच भी बुलाया जाता है।

सबसे आसान समाधान एक समर्पित कनेक्शन का उपयोग करना है जो लॉक की अवधि के लिए खुला रहता है और अन्य एसक्यूएल के लिए इसका उपयोग नहीं करता है अगर एपीआई किसी भी sp_resest_connection कॉल को इंटरलीव कर सकता है (प्रोफाइलिंग यह साबित कर सकता है)।

इसके अलावा SqlConnection vs Sql Session. Do their lifetimes coincide?

और what-does-exec-sp-reset-connection-mean-in-sql-server-profiler

यहाँ देख रहा है एक सी # sp_GetAppLock() आवरण हम कोशिश कर रहे हैं (अस्वीकरण - अच्छी तरह से परीक्षण नहीं):

उपयोग:

using (new globalApplicationSqlServerLock(connectionString, "theUniqueLockName") 
{ 
    // protected code 
} 

प्रवर्तन:

using System; 
using System.Data; 
using System.Data.SqlClient; 

namespace CompanyNamespace.Server.DataAdaptersCommon 
{ 
    /// <summary> 
    /// A wrapper around sp_GetAppLock (SQL Server), useful for global locking (by arbitrary name) across multiple machines. 
    /// For instance: Include the compatibility version number within the lock resource to create a version specific lock. 
    /// </summary> 
    public class GlobalApplicationSqlServerLock : SimplifiedDisposableBase 
    { 
     /// <summary> 
     /// Last returned value from sp_GetAppLock() or sp_ReleaseAppLock() 
     /// </summary> 
     public Int32 LastReturnCode { get; private set; } 

     /// <summary> 
     /// The SQL Connection to use. 
     /// </summary> 
     readonly SqlConnection _connection; 

     /// <summary> 
     /// The name of the lock chosen by the callse 
     /// </summary> 
     readonly string _lockName; 

     /// <summary> 
     /// The cumulative times that Lock() has been called. 
     /// </summary> 
     int _lockCount; 

     // Refer to sp_GetAppLock() 
     // 
     const string _lockOwner = "session"; 
     const string _lockMode = "Exclusive"; 
     const string _dbPrincipal = "public"; 

     /// <summary> 
     /// Wait a maximum of this many seconds. 
     /// </summary> 
     Int32 _waitForLockMaxSeconds; 

     /// <summary> 
     /// Constructor accepting a Connection String 
     /// </summary> 
     /// <param name="connectionString">Connection string should include "...;AppName=AppType,WebPid" to improve DB side logging.</param> 
     /// <param name="lockName"></param> 
     /// <param name="waitForLockMaxSeconds">Throw an exception if the lock cannot be acquired within this time period.</param> 
     /// <param name="lockNow">True to obtain the lock via the contructor call. Lock is always released in Dipose()</param> 
     /// <param name="excludeFromOpenTransactionScope">True to exclude from any open TransactionScope</param> 
     public GlobalApplicationSqlServerLock(
      string connectionString, 
      string lockName, 
      Int32 waitForLockMaxSeconds = 30, 
      bool lockNow = true, 
      bool excludeFromOpenTransactionScope = true) 
     { 
      SqlConnectionStringBuilder conStrBuilder = new SqlConnectionStringBuilder(connectionString); 

      if (excludeFromOpenTransactionScope) 
       conStrBuilder.Enlist = false; 

      _waitForLockMaxSeconds = waitForLockMaxSeconds; 

      // The lock must use a dedicated connection that stays open for the duration of the lock. 
      // Otherwise, since the lock owner is "session", when the connection is closed the lock "may" be 
      // released since connection pooling could have inconsistent side effects. So caller must ensure 
      // lock is released (using IDisposable, etc). 
      // 
      _connection = new SqlConnection(conStrBuilder.ConnectionString);   
      _connection.Open(); 
      _lockName = lockName; 

      if (lockNow) 
       Lock(); 
     } 

     /// <summary> 
     /// Lock 
     /// </summary> 
     public void Lock() 
     { 
      string errMsg; 
      if (!TryLock(out errMsg)) 
       throw new Exception(errMsg); 
     } 

     /// <summary> 
     /// Try lock 
     /// </summary> 
     /// <param name="errMsg"></param> 
     /// <returns>True if lock obtained, false if not with error message.</returns> 
     public bool TryLock(out string errMsg) 
     {      
      using (SqlCommand command = new SqlCommand()) 
      { 
       command.Connection = _connection; 
       command.CommandType = CommandType.StoredProcedure; 

       command.CommandText = "sp_GetAppLock"; 
       command.Parameters.Add(new SqlParameter("@Resource", SqlDbType.NVarChar, 255) { Value = _lockName }); 
       command.Parameters.Add(new SqlParameter("@LockMode", SqlDbType.NVarChar, 32) { Value = _lockMode }); 
       command.Parameters.Add(new SqlParameter("@LockOwner", SqlDbType.NVarChar, 32) { Value = _lockOwner }); 
       command.Parameters.Add(new SqlParameter("@LockTimeout", SqlDbType.Int) { Value = _waitForLockMaxSeconds }); 
       command.Parameters.Add(new SqlParameter("@DBPrincipal", SqlDbType.NVarChar, 128) { Value = _dbPrincipal }); 
       command.Parameters.Add(new SqlParameter("@Result", SqlDbType.Int) { Direction = ParameterDirection.ReturnValue }); 

       command.ExecuteNonQuery(); 

       LastReturnCode = (int)command.Parameters["@Result"].Value; 
      } 

      switch (LastReturnCode) 
      { 
       case 0: 
       case 1: 
        _lockCount++; 
        errMsg = null; 
        return true; 
       case -1: 
        errMsg = "The lock request timed out."; 
        break; 
       case -2: 
        errMsg = "The lock request was canceled."; 
        break; 
       case -3: 
        errMsg = "The lock request was chosen as a deadlock victim."; 
        break; 
       case -999: 
        errMsg = "Indicates a parameter validation or other call error."; 
        break; 
       default: 
        errMsg = "Unexpected return value"; 
        break; 
      } 

      return false;    
     } 

     /// <summary> 
     /// Release the lock 
     /// </summary> 
     public void Release() 
     { 
      string errMsg; 

      using (SqlCommand command = new SqlCommand()) 
      { 
       command.Connection = _connection; 
       command.CommandType = CommandType.StoredProcedure; 

       command.CommandText = "sp_ReleaseAppLock"; 
       command.Parameters.Add(new SqlParameter("@Resource", SqlDbType.NVarChar, 255) { Value = _lockName }); 
       command.Parameters.Add(new SqlParameter("@LockOwner", SqlDbType.NVarChar, 32) { Value = _lockOwner }); 
       command.Parameters.Add(new SqlParameter("@DBPrincipal", SqlDbType.NVarChar, 128) { Value = _dbPrincipal }); 
       command.Parameters.Add(new SqlParameter("@Result", SqlDbType.Int) { Direction = ParameterDirection.ReturnValue }); 

       command.ExecuteNonQuery(); 
       LastReturnCode = (int)command.Parameters["@Result"].Value; 
      } 

      switch (LastReturnCode) 
      { 
       case 0: 
        _lockCount--; 
        return; 
       case -999: 
        errMsg = "Indicates a parameter validation or other call error."; 
        break; 
       default: 
        errMsg = "Unexpected return value"; 
        break; 
      } 

      throw new Exception(errMsg); 
     } 

     /// <summary> 
     /// Disposable implmentation 
     /// </summary> 
     protected override void FreeManagedResources() 
     {    
      try 
      { 
       while (_lockCount > 0) 
        Release(); 
      } 
      finally 
      { 
       try 
       { 
        if (_connection != null && _connection.State != ConnectionState.Closed) 
         _connection.Close(); 
       } 
       finally 
       { 
        base.FreeManagedResources(); 
       } 
      }    
     } 
    } 
} 


using System; 
using System.Collections.Generic; 
using System.Diagnostics; 

namespace CompanyNamespace.Common 
{ 
    /// <summary> 
    /// To support IDisposable, pass true to constructor and call: 
    /// 
    ///   AutoDispose(IDisposable) for each disposable at time of creation, 
    ///   
    /// Or override these as needed: 
    /// 
    ///   FreeManagedResources() and 
    ///   FreeUnmanagedResources() 
    ///   
    /// Multi-thread safe. 
    /// </summary> 
    public abstract class SimplifiedDisposableBase : IDisposable 
    { 
     /// <summary> 
     /// Flag for IDisposable 
     /// </summary> 
     protected bool _isDisposed = false; 

     /// <summary> 
     /// List of items that should be Dispose() when the instance is Disposed() 
     /// </summary> 
     private List<IDisposable> _autoDisposables = new List<IDisposable>(); 

     /// <summary> 
     /// Constructor 
     /// </summary> 
     public SimplifiedDisposableBase() 
     { 
     } 

     /// <summary> 
     /// Finalizer (needed for freeing unmanaged resources and adds a check a Dispose() check for managed resources). 
     /// </summary> 
     ~SimplifiedDisposableBase() 
     { 
      // Warning: An exception here will end the application. 
      // Do not attempt to lock to a possibly finalized object within finalizer 
      // http://stackoverflow.com/questions/4163603/why-garbage-collector-takes-objects-in-the-wrong-order    

      string errMessages = string.Empty; 
      try 
      { 
       errMessages = String.Format("Warning: Finalizer was called on class '{0}' (base class '{1}'). " + 
        "IDisposable's should usually call Dispose() to avoid this. (IsDisposed = {2})", 
        GetType().FullName, 
        typeof(SimplifiedDisposableBase).FullName, 
        _isDisposed); 

       Debug.WriteLine(errMessages); 

       Dispose(false);  // free any unmanaged resources     
      } 
#if DEBUG 
      catch (Exception ex) 
      { 
       errMessages = "Fatal: Exception occurred within Finalizer ~" + GetType().FullName + "()." + errMessages; 
       Debug.WriteLine(errMessages + " " + ex.Message); 

       // Verified that this exception appears in Windows Event Log and includes the originating class message and StackTrace[0] 
       throw new Exception(errMessages, ex); 
      } 
#else 
      catch (Exception) 
      { 
       /* Don't exit the application */ 
      } 
#endif 
     } 

     /// <summary> 
     /// Add an managed item to be automatically disposed when the class is disposed. 
     /// </summary> 
     /// <param name="disposable"></param> 
     /// <returns>The argument</returns> 
     public T AutoDispose<T>(T disposable) where T : IDisposable 
     { 
      lock (_autoDisposables) 
       _autoDisposables.Add(disposable); 

      return disposable; 
     }   

     /// <summary> 
     /// Derived class can override and chain for support of IDisposable managed resources. 
     /// </summary> 
     protected virtual void FreeManagedResources() 
     { 
      lock (_autoDisposables) 
      { 
       _autoDisposables 
        .ForEach(d => d.Dispose()); 

       _autoDisposables.Clear(); 
      } 
     } 

     /// <summary> 
     /// Derived class can optionally override for support of IDisposable unmanaged resources. 
     /// </summary> 
     protected virtual void FreeUnmanagedResources() { } 

     /// <summary> 
     /// Standard IDisposable Implmentation 
     /// </summary> 
     public void Dispose() 
     { 
      Dispose(true); // calling multiple times is okay 
      GC.SuppressFinalize(this); // http://stackoverflow.com/questions/12436555/calling-suppressfinalize-multiple-times is okay 
     } 

     /// <summary> 
     /// Dispose 
     /// </summary> 
     protected virtual void Dispose(bool isDisposing)   
     {    
      if (!isDisposing) // if called from finalizer, do not use lock (causes exception) 
      { 
       if (!_isDisposed) 
       { 
        FreeUnmanagedResources(); // always free these     
        _isDisposed = true; 
       } 
       return; 
      } 

      // Remainder is called from IDisposable (not finalizer) 

      // Based on "Implemenent IDisposable Correctly" 
      // http://msdn.microsoft.com/en-us/library/ms244737.aspx 

      lock (_autoDisposables) 
      { 
       if (_isDisposed) 
        return; // the docs specifically state that Dispose() must be callable multiple times without raising an exception 

       try 
       { 
        try 
        { 
         FreeManagedResources(); 
        } 
        finally 
        { 
         FreeUnmanagedResources(); // always free these 
        } 
       } 
       finally 
       { 
        _isDisposed = true; 
       } 
      } 
     }  
    }  
} 
+0

'sp_reset_connection' वास्तव में Linq2Sql के साथ हर बार कहा जाता है। लेकिन अगर सक्रिय 'ट्रांज़ेक्शनस्कोप' है तो यह * नहीं होता है। – GSerg

संबंधित मुद्दे