具有数据库访问权限的Soap Web Service

时间:2013-07-18 14:22:51

标签: c# .net sql-server web-services asmx

所以基本上我有一个Soap webservice,可以从SqlServer数据库中插入和检索一些数据。

webservice使用负责数据库内容的Singleton。

public class Service : System.Web.Services.WebService
{
    private DBAccess dbaccess;
    public Service()
    {
        dbaccess = DBAccessLocalhost.GetInstance();
    }

    [WebMethod]
    public List<Profile> XXX(Guid a, uint b, DateTime c)
    {
        return dbaccess.XXX(a, b, c);
    }
    ...
}

访问数据库的单例。它有很多方法基本上都可以做到这一点。

public class DBAccessLocalhost : DBAccess
{
    private static DBAccess instance = null;
    private string connectionString;

    public static DBAccess GetInstance()
    {
        if (instance == null)
            instance = new DBAccessLocalhost();

        return instance;
    }

    private DBAccessLocalhost()
    {
        connectionString = "Data Source=localhost;Initial Catalog=DBName;Integrated Security=True;Max Pool Size=2000;Pooling=false";
    }

    public override void XXX(Guid a, uint b, DateTime c)
    {
        SqlCommand cmd;
        SqlDataReader dr;
        string strSql = "SP_Name";
        SqlConnection conn;
        conn = new SqlConnection(connectionString);

        try
        {
            conn.Open();

            cmd = new SqlCommand(strSql, conn);
            cmd.CommandType = System.Data.CommandType.StoredProcedure;

            cmd.Parameters.AddWithValue("@a", a.ToString());
            cmd.Parameters.AddWithValue("@b", (int)b);
            cmd.Parameters.AddWithValue("@c", c);
            dr = cmd.ExecuteReader();

            while (dr.Read() && dr.HasRows)
            {
                //Do stuff...
            }

            dr.Close();

        }catch (Exception ex)
        {
            throw new DBError("Errors: " + ex.Message);
        }
        finally
        {
            conn.Close();
        }
     }

第二版:

public class DBAccessLocalhost : DBAccess
{
    private static DBAccess instance = null;
    private string connectionString;

    public static DBAccess GetInstance()
    {
        if (instance == null)
            instance = new DBAccessLocalhost();

        return instance;
    }

    private DBAccessLocalhost()
    {
        connectionString = "Data Source=localhost;Initial Catalog=DBName;Integrated Security=True;Max Pool Size=2000;Pooling=true";
    }

    public override void XXX(Guid a, uint b, DateTime c)
    {
        string strSql = "SP_Name";

        using (SqlConnection conn = new SqlConnection(connectionString))
        {
            using (SqlCommand cmd = new SqlCommand(strSql, conn))
            {
                cmd.CommandType = System.Data.CommandType.StoredProcedure;

                cmd.Parameters.AddWithValue("@a", a.ToString());
                cmd.Parameters.AddWithValue("@b", (int)b);
                cmd.Parameters.AddWithValue("@c", c);

                try
                {
                    conn.Open();

                    using (SqlDataReader dr = cmd.ExecuteReader())
                    {
                        while (dr.Read())
                        {
                            //....
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new DBError("Error: " + ex.Message);
                }
                finally
                {
                    conn.Close();
                }
            }
        }

问题是有时我会遇到这个例外:

 DBAccessWebSerice,System.ServiceModel.FaultException: Server was unable to process 
 request. ---> Errors: ExecuteNonQuery requires an open and available    
 Connection. The connection's current state is closed (Sometimes says connecting).

 Server stack trace: 
 at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime 
 operation, ProxyRpc& rpc)
 at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, 
 ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
 at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage  
 methodCall, ProxyOperationRuntime operation)
 at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

问题可能是因为数据库同时存在多个连接?

可能这不是最好的办法。但是,如果您有更好的方法来解决这个问题,请说明一下。

2 个答案:

答案 0 :(得分:2)

Singleton不是Db Access的好模式。使用静态方法检索数据。最有可能的是,您的问题是2个线程访问了您的实例,并且您无法阻止它。 其他要点:

  1. 不要使用“SP_”前缀
  2. 命名存储过程
  3. 在处理诸如datareader
  4. 之类的一次性对象时使用using
  5. 使用连接池
  6. 我发现您在此处提供的代码中存在各种问题的可能性。例如,如果读取datareader错误,则不会清除资源。

    这是一种方式:

    public override void XXX(Guid a, uint b, DateTime c)
    {
        string strSql = "SP_Name";
        lock (lockObject) {  // lock the code for the duration of execution so it is thread safe now (because you in singleton) 
    // But this is bad for DB access !!!! What if SP executes long - other threads have to wait
    // Use simple static methods for data access and no `lock`
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand(strSql, conn))
                {
                    cmd.CommandType = System.Data.CommandType.StoredProcedure;
    
                    cmd.Parameters.AddWithValue("@a", a.ToString());
                    cmd.Parameters.AddWithValue("@b", (int)b);
                    cmd.Parameters.AddWithValue("@c", c);
                    // no need for any `try-catch` here because `using` is that and `finally`
                    using (SqlDataReader dr = cmd.ExecuteReader())
                    {
                        while (dr.Read())
                        {
                            //....
                        }
                    }
    
                }
                conn.Close();
            }
        }
    }
    

    其他选项,如果您希望更好地控制异常处理,则使用try-catch-finally代替using。然后,最后,您将执行另一个try-catch来清理资源。    //伪代码

    try
           connection
           command
           datareader
       catch (Exception ex)
           prepare user message
           log or
           throw/throw new, etc // depends. This is Webservice, may be log and response with error message
       finally
           try
              if datareader alive
                 close datareader 
                 dispose datareader 
           catch // do nothing here
           try
              if command alive
                 dispose command
           catch // do nothing here
           try
              if connection alive
                 close connection 
                 dispose connection 
           catch // do nothing here
    

答案 1 :(得分:0)

这是一种方法,放弃单身人士的想法,正如另一个答案所暗示的那样:

  • 将Func传递给您的服务,该服务是数据访问对象的工厂
  • 让DBAccess实现IDisposable并使用using块

    public class Service : System.Web.Services.WebService
    {
       private Func<DBAccess> getDBAccess;
    
       public Service(Func<DBAccess> getDBAccess)
       {
          this.getDBAccess = getDBAccess;
       }
    
       [WebMethod]
       public List<Profile> XXX(Guid a, uint b, DateTime c)
       {
            using(var dbaccess = this.getDBAccess())
            {
                return dbaccess.XXX(a, b, c);
            }  
       }
       ...
     }