我正在寻找一个C#SQL Server包装器来调用一些存储过程。如果我正在写一个函数,我会做类似下面的事情(我认为是正确的/正确的):
void RunStoredProc1(object arg1)
{
using(SqlConnection conn = new SqlConnection(connStr)){
try{
SqlCommand cmd = new SqlCommand("storedProc1", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@input1", arg1);
conn.Open();
cmd.ExecuteNonQuery();
} catch (Exception ex){
//handle the exception appropriately.
}
}
}
我遇到的问题是它似乎有很多重复的代码......每个函数都有相同的using / try(open / execute)/ catch代码,并且很高兴拥有它们只在一个地方。这样做有干净的方法吗?对于我想要使用数据读取器的查询怎么样?
答案 0 :(得分:5)
这样的事情应该做:
void RunStoredProc(string storedProcName, IDictionary<string, object> args)
{
using (SqlConnection conn = new SqlConnection(connStr))
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = storedProcName;
cmd.CommandType = CommandType.StoredProcedure;
foreach (KeyValuePair<string, object> kvp in args)
{
cmd.Parameters.AddWithValue(kvp.Key, kvp.Value);
}
conn.Open();
cmd.ExecuteNonQuery();
}
}
连接对象本身也可能更适合作为此辅助方法的参数,因此您可以将其设为static
。将其作为SqlConnection
上的扩展方法编写可能会很有趣。
我会在RunStoredProc1
方法中保留异常处理,甚至更可能:在调用RunStoredProc1
的方法中,因为异常处理可能会因具体情况而异。
void RunStoredProc1(object input1)
{
var args = new Dictionary<string, object>()
{
{ "input1", input1 }
};
try
{
RunStoredProc("storedProc1", args);
}
catch (Exception ex)
{
// Handle exception properly
}
}
答案 1 :(得分:2)
对我来说只是一个有趣的练习,而不一定是你想要实现它的方式。我写了一个快速流畅的界面来构建和执行SqlCommands。
几个示例用法:
int i = Sql.UsingConnection("sample")
.GetTextCommandFor("Select Top 1 ActorID From Actor Where FirstName = @fname")
.AddParameters(new {fname = "Bob"})
.OnException(e => Console.WriteLine(e.Message))
.ExecuteScalar<int>();
var q = Sql.UsingConnection("sample")
.GetTextCommandFor("Select * From Actor Where FirstName=@fname and ActorID > @id")
.AddParameters(new {id = 1000, fname = "Bob"});
using(var reader = q.ExecuteReader())
{
while(reader.Read())
{
// do something
}
}
实际的类和接口如下:
public class Sql
{
public static ISqlCommandTypeSelector UsingConnection(string connection)
{
return new SqlBuilder(connection);
}
private class SqlBuilder : ISqlCommandTypeSelector, ISqlParameterManager, ISqlExecutor
{
private string _connection;
private string _sqltext;
private CommandType _commandtype;
private Action<Exception> _exceptionBehavior = DefaultExceptionBehavior;
private IList<SqlParameter> _inParams;
public SqlBuilder(string connection)
{
_connection = ConfigurationManager.ConnectionStrings[connection].ConnectionString;
_inParams = new List<SqlParameter>();
}
public ISqlParameterManager GetTextCommandFor(string text)
{
_sqltext = text;
_commandtype = CommandType.Text;
return this;
}
public ISqlParameterManager GetProcCommandFor(string proc)
{
_sqltext = proc;
_commandtype = CommandType.StoredProcedure;
return this;
}
public ISqlExecutor OnException(Action<Exception> action)
{
_exceptionBehavior = action;
return this;
}
public void ExecuteNonQuery()
{
try
{
using (var connection = new SqlConnection(_connection))
using (var cmd = connection.CreateCommand())
{
ConfigureCommand(cmd);
PopulateParameters(cmd);
connection.Open();
cmd.ExecuteNonQuery();
}
}
catch(Exception ex)
{
_exceptionBehavior(ex);
}
}
public T ExecuteScalar<T>()
{
T result = default(T);
try
{
using (var connection = new SqlConnection(_connection))
using (var cmd = connection.CreateCommand())
{
ConfigureCommand(cmd);
PopulateParameters(cmd);
connection.Open();
result = (T) cmd.ExecuteScalar();
return result;
}
}
catch(InvalidCastException ex)
{
// rethrow?
}
catch(Exception ex)
{
_exceptionBehavior(ex);
}
return result;
}
public IDataReader ExecuteReader()
{
try
{
var connection = new SqlConnection(_connection);
var cmd = connection.CreateCommand();
ConfigureCommand(cmd);
PopulateParameters(cmd);
connection.Open();
var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return reader;
}
catch(Exception ex)
{
_exceptionBehavior(ex);
}
return null;
}
public ISqlExecutor AddParameters(object @params)
{
var type = @params.GetType();
var props = type.GetProperties();
foreach (var propertyInfo in props)
{
var param = new SqlParameter("@" + propertyInfo.Name, propertyInfo.GetValue(@params, null));
param.Direction = ParameterDirection.Input;
_inParams.Add(param);
}
return this;
}
public ISqlExecutor WithoutParams()
{
return this;
}
private void ConfigureCommand(SqlCommand cmd)
{
cmd.CommandText = _sqltext;
cmd.CommandType = _commandtype;
}
private void PopulateParameters(SqlCommand cmd)
{
cmd.Parameters.AddRange(_inParams.ToArray());
}
private static void DefaultExceptionBehavior(Exception e)
{
// do something
}
}
}
public interface ISqlCommandTypeSelector
{
ISqlParameterManager GetTextCommandFor(string text);
ISqlParameterManager GetProcCommandFor(string proc);
}
public interface ISqlExecutor
{
ISqlExecutor OnException(Action<Exception> action);
void ExecuteNonQuery();
T ExecuteScalar<T>();
IDataReader ExecuteReader();
}
public interface ISqlParameterManager
{
ISqlExecutor AddParameters(object @params);
ISqlExecutor WithoutParams();
}
如果您真的讨厌重复的代码,有一些重复的代码可能会被重构。这只是一个有趣的练习,但可能不是您想要如何进行数据访问。这也不支持输出参数。
答案 2 :(得分:1)
如果您坚持使用纯ADO.NET作为数据层,Microsoft企业库数据访问应用程序块可以帮助减少冗余代码。见http://msdn.microsoft.com/en-us/library/ff664408(v=PandP.50).aspx。网上和下载中都有很多代码示例,即http://msdn.microsoft.com/en-us/library/ff664702(v=PandP.50).aspx。
答案 3 :(得分:1)
我非常喜欢让电脑做死记硬背的重复性工作。他们非常擅长。我只需教他们做一次。所以我编写了一个代码生成器,它使用参考数据库生成强类型访问代码。这种技术的优点是,如果您更改存储过程的签名,您只需要重新生成数据访问层。任何重大更改都将导致编译错误。
我写的代码生成读取了一个XML文件,用于标识感兴趣的存储过程,并从指定的参考数据库中检索它们的元数据。
XML文件包含标识每个存储过程是否返回的标志
从中生成适当的代码,每个存储过程1个类。生成的代码提供对存储过程的返回代码的访问以及任何输出参数的返回值。
它还解析存储过程源代码中存储过程的声明,以识别任何可选参数(具有默认值的参数):生成的代码允许在调用中省略那些执行存储过程的参数。
调用生成的代码如下:
public DataTable GetRiskFactorsForPatient( int patientID )
{
dbo_GetRiskbyPatient sp = new dbo_GetRiskbyPatient( CONNECT_STRING_ID ) ;
int rc = sp.Exec( patientID ) ;
DataTable dt = sp.ResultSet ;
if ( dt == null ) throw new InvalidOperationException( "nothing returned from stored procedure" ) ;
return dt ;
}
答案 4 :(得分:0)
就个人而言,我更喜欢
void RunStoredProc1(object arg1)
{
try
{
using(SqlConnection conn = new SqlConnection(connStr))
{
using SqlCommand cmd = new SqlCommand("storedProc1", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@input1", arg1);
conn.Open();
cmd.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
//handle the exception appropriately.
}
}
在传统的尝试中,最后你需要做的是管理你的资源。
但总的来说,我喜欢用不同的方法来实现它,这样你就可以为sproc定制你的catch块。
此外,您可能需要一个以上的参数,而您只是将一个相当简单的功能搞得一团糟