简体   繁体   中英

SqlDependency in ASP.NET

I created the following encapsulation over the SQL Dependency object:

public class DependencyTracker
    {
        private SqlDependency _SQLDependency = null;

        public string ConnectionString
        { get; private set; }

        public string CommandNotifier
        { get; private set; }

        public delegate void Refresh();
        public event Refresh OnRefresh;

        public DependencyTracker(string connectionString, string commandNotifier)
        {
            ConnectionString = connectionString;
            CommandNotifier = commandNotifier;
        }

        public void StartDependency()
        {
            SqlDependency.Start(ConnectionString);
        }

        public void StopDependency()
        {
            SqlDependency.Stop(ConnectionString);
        }

        public void TrackForChanges()
        {
            using (SqlConnection sqlConn = new SqlConnection(ConnectionString))
            {
                sqlConn.Open();
                using (SqlCommand sqlCommand = new SqlCommand(CommandNotifier, sqlConn))
                {
                    sqlCommand.CommandType = CommandType.StoredProcedure;
                    _SQLDependency = new SqlDependency(sqlCommand);
                    _SQLDependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
                    sqlCommand.ExecuteReader();
                }
            }
        }

        void dependency_OnChange(object sender, SqlNotificationEventArgs e)
        {
            SqlDependency sqlDependency = (SqlDependency)sender;
            sqlDependency.OnChange -= dependency_OnChange;

            if (OnRefresh != null)
            {
                OnRefresh();
            }
        }

        public bool HasChanges
        {
            get
            {
                return _SQLDependency.HasChanges;
            }
        }
    }

It's not an original work, it's based on this text. From my repository I do the following:

public bool Updatexxx(Ixxx xsxs)
        {
            try
            {
                SqlConnection sqlConn = new SqlConnection(_ConnectionString);
                sqlConn.Open();
                ...

                bool result = sqlComm.ExecuteNonQuery() == 1;

                _ResetEvent.WaitOne();

                return result;
            }
            catch ...
            catch ...
        }

The callback is

public void RefreshData()
        {
            FindAllxxx();
            _ResetEvent.Set();
        }

And

public HashSet<Iddd> Finddadas()
        {
            DependencyTracker tracker = _Container.Resolve<DependencyTracker>("dada");
            UnityHashSet<IT24Route> hash = _Container.Resolve<UnityHashSet<dadas>>("Tdsadas");
            if (tracker.HasChanges || hash.Count == 0)
            {
                hash = new UnityHashSet<dsda>(_Container);
                hash.ImportHashSet(FindAlldsdsNonCached());
                _Container.RegisterInstance<UnityHashSet<dsds>>("dasda", hash);
                tracker.TrackForChanges();
            }
            return hash;
        }

and FindAllXXXNonCached() does the real select from database. As you see, I am caching everything. My question is why does this NOT work. The symptom: from dependecy tracker the callback is called twice then it block. I implemented this because my notifications were coming after the refresh of the page began. I tried to put a manual reset event to give notification a chance, then set the manual reset event and the refresh the UI. As I said, after passing twice through the OnChange, it freezes. What can I do? Dependency Tracker is instance registered in Unity container, Repository is type registered.

void dependency_OnChange(object sender, SqlNotificationEventArgs e)

You are expected to inspect the SqlNotificationEventArgs and see what you're notified for, data change or something else. Check the Info to be Insert/Update/Delete . Check the Source to be Data . Check the Type to be Change .

Most likely your query is signalled immedeatly that it does not conform to the restrictions imposed on Query Notifications . Yes, I know that link points to Indexed Views, and if you want to understand why, read The Mysterious Notification .

You also have a race condition between your Update waiting for _ResetEvent and callback signaling _ResetEvent. T1 calls Update. Meantime an unrelated update occurs in the data and callback is called. _ResetEvent is set. T1 finishes the update and waits on _ResteEvent, which is signaled, so it proceeds. The caller assumes the callback for its own update occured and cache is refreshed, but this is not true.

A second more serious problem is that the code is incorrect in the presence of transactions. UpdateXXX assumes the callback for its own update will occur immedeatly and waits for it. The query notification will be delivered by the engine only after the Update commits, so when a TransactionScope is present the UpdateXXX method will block waiting for a notification cannot come until UpdateXXX returns (live deadlock).

Also is not clear what is the purpose of TrackForChanges. You are reading a SqlDataReader (sqlCommand.ExecuteReader) but ignore the result. With Query Notifications you submit a query , read itse result and you'll be notified when that result has changed.

And finally, never read data in the SqlDependency notification callback .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM