这个共享的DbCommand对象线程是否安全?

时间:2011-04-11 01:56:36

标签: c# asp.net multithreading thread-safety data-access-layer

我不明白为什么每次需要调用存储过程时都必须创建一个DbCommand对象。所以我想找到一种方法来做到这一点。我测试了我的代码(见下文)。但我想与社区核实,以防我错过了什么。我将在ASP.NET应用程序中使用它。这段代码是否安全?

SharedDbCommand - 包装DbCommand对象的创建和存储

Db - 数据库的包装器,通过静态字段和ThreadStatic属性使用SharedDbCommand

程序 - 启动线程并使用Db对象

的控制台应用程序
// SharedDbCommand.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data;

namespace TestCmdPrepare {
    public class SharedDbCommand {
        [ThreadStatic]
        static DbCommand cmd;

        public SharedDbCommand(string procedureName, ConnectionStringSettings dbConfig) {
            var factory = DbProviderFactories.GetFactory(dbConfig.ProviderName);
            cmd = factory.CreateCommand();
            cmd.Connection = factory.CreateConnection();
            cmd.Connection.ConnectionString = dbConfig.ConnectionString;
            cmd.CommandText = procedureName;
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
            if (cmd is SqlCommand) {
                try {
                    cmd.Connection.Open();
                    SqlCommandBuilder.DeriveParameters(cmd as SqlCommand);
                } finally {
                    if (cmd != null && cmd.Connection != null) 
                        cmd.Connection.Close();
                }
            }
        }

        public DbParameter this[string name] {
            get {
                return cmd.Parameters[name];
            }
        }

        public IDataReader ExecuteReader() {
            try {
                cmd.Connection.Open();
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            } finally {
                cmd.Connection.Close();
            }
        }

        public void ExecuteNonQuery() {
            try {
                cmd.Connection.Open();
                cmd.ExecuteNonQuery();
            } finally {
                cmd.Connection.Close();
            }
        }
    }
}

// Db.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.Common;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
using System.Diagnostics;

namespace TestCmdPrepare {
    public class Db {
        ConnectionStringSettings dbSettings;
        DbProviderFactory factory;
        public Db() {
            dbSettings = ConfigurationManager.ConnectionStrings["db"];
            factory = DbProviderFactories.GetFactory(dbSettings.ProviderName);
        }
        IDataReader ExecuteReader(DbCommand cmd) {
            cmd.Connection.Open();
            return cmd.ExecuteReader(CommandBehavior.CloseConnection);
        }

        private DbConnection CreateConnection() {
            var c = factory.CreateConnection();
            c.ConnectionString = dbSettings.ConnectionString;
            return c;
        }

        DbCommand CreateCommand(string procedureName) {
            var cmd = factory.CreateCommand();
            cmd.Connection = CreateConnection();
            cmd.CommandText = "get_stuff";
            cmd.CommandType = CommandType.StoredProcedure;
            if (cmd is SqlCommand) {
                try {
                    cmd.Connection.Open();
                    SqlCommandBuilder.DeriveParameters(cmd as SqlCommand);
                } finally {
                    cmd.Connection.Close();
                }
            }
            return cmd;
        }


        [ThreadStatic]
        static DbCommand get_stuff;

        DbCommand GetStuffCmd {
            get {
                if (get_stuff == null)
                    get_stuff = CreateCommand("get_stuff");
                return get_stuff;
            }
        }

        public string GetStuff(int id) {
            GetStuffCmd.Parameters["@id"].Value = id;
            using (var reader = ExecuteReader(GetStuffCmd)) {
                if (reader.Read()) {
                    return reader.GetString(reader.GetOrdinal("bar"));
                }
            }
            return null;
        }

        [ThreadStatic]
        static SharedDbCommand get_stuff2;
        public string GetStuff2(int id) {
            if (get_stuff2 == null)
                get_stuff2 = new SharedDbCommand("get_stuff", dbSettings);
            get_stuff2["@id"].Value = id;
            using (var reader = get_stuff2.ExecuteReader()) {
                if (reader.Read()) {
                    Thread.Sleep(1000);
                    return reader.GetString(reader.GetOrdinal("bar"));
                }
            }
            return null;
        }
    }
}


// Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Configuration;
using System.Data.SqlClient;
using System.Threading;

namespace TestCmdPrepare {
    class Program {
        static void Main(string[] args) {
            var db = new Db();
            var threads = new List<Thread>();
            for (int i = 0; i < 4; i++) {
                var one = new Thread(Run2);
                var two = new Thread(Run1);

                threads.Add(one);
                threads.Add(two);
                one.Start();
                two.Start();

                Write(db, 1);
                Write(db, 2);
            }
            Console.WriteLine("Joining");
            foreach (var thread in threads) {
                thread.Join();
            }
            Console.WriteLine();
            Console.WriteLine("DONE");
            Console.ReadLine();
            return;
        }

        static void Write(Db db, int id) {


       Console.WriteLine("2:{0}:{1}", Thread.CurrentThread.ManagedThreadId, db.GetStuff2(id));
        Console.WriteLine("1:{0}:{1}", Thread.CurrentThread.ManagedThreadId, db.GetStuff(id));
    }

    static void Run1() {
        var db = new Db();
        Write(db, 1);
    }

    static void Run2() {
        var db = new Db();
        Write(db, 2);
    }

    }
}

2 个答案:

答案 0 :(得分:1)

出于很多原因的坏主意。其他人已经提到了其中一些,但我会给你一个特定的实现:在ASP.NET中使用ThreadStatic 最终咬你(见here)。你没有控制管道,所以多个线程的可能最终为一个请求提供服务(在页面上的事件处理程序之间思考等等 - 运行了多少不是你的代码?) 。由于未处理的异常,在您不拥有的请求线程上孤立数据也很容易。可能不是一个showstopping问题,但最好的情况是你看到内存泄漏和增加服务器资源使用,而你的连接只是坐在那里...

我建议你让ConnectionPool完成它的工作 - 它有一些瑕疵,但与ASP.NET管道中的其他内容相比,性能不是其中之一。如果你真的想这样做,至少考虑在HttpContext.Current.Items中存储连接对象。你可以在有限的情况下完成这项工作,但总会有问题,特别是如果你开始编写并行代码。

来自一个曾经去过那里的人只需0.02美元。 :)

答案 1 :(得分:0)

这不是保持DbCommand创建的好习惯。此外,由于线程处理逻辑,这使得您的应用程序非常复杂。

正如Microsoft建议您在执行查询后立即创建和配置连接和命令对象。它们非常轻巧。不需要保留与它们一起使用的内存 - 在查询执行完成并且已经获取所有结果时将它们处理掉。