我正在编写代码生成器,并且在确定存储过程结果集Column的可空状态时遇到困难。我可以很好地查询DataType,但是datareader对象和数据表列都没有包含我的列的正确可空值。
public List<DataColumn> GetColumnInfoFromStoredProcResult(string schema, string storedProcName)
{
//build sql text
var sb = new StringBuilder();
sb.Append("SET FMTONLY OFF; SET FMTONLY ON; \n");//this is how EF4.1 did so I copied..not sure why the repeat
sb.Append(String.Format("exec {0}.{1} ", schema, storedProcName));
var prms = GetStoredProcedureParameters(schema: schema, sprocName: storedProcName);
var count = 1;
foreach (var param in prms)
{
sb.Append(String.Format("{0}=null", param.Name));
if (count < prms.Count)
{
sb.Append(", ");
}
count++;
}
sb.Append("\n SET FMTONLY OFF; SET FMTONLY OFF;");
var dataTable = new DataTable();
//var list = new List<DataColumn>();
using (var sqlConnection = this.SqlConnection)
{
using (var sqlAdapter = new SqlDataAdapter(sb.ToString(), sqlConnection))
{
if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open();
sqlAdapter.SelectCommand.ExecuteReader(CommandBehavior.KeyInfo);
sqlConnection.Close();
sqlAdapter.Fill(dataTable);
}
//using (var sqlCommand = new SqlCommand())
//{
// sqlCommand.CommandText = sb.ToString();
// sqlCommand.CommandType = CommandType.Text;
// sqlCommand.Connection = sqlConnection;
// if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open();
// var dr = sqlCommand.ExecuteReader(CommandBehavior.SchemaOnly);
// var whateva = dr.GetSchemaTable();
// foreach (DataColumn col in whateva.Columns)
// {
// list.Add(col);
// }
//}
}
var list = dataTable.Columns.Cast<DataColumn>().ToList();
return list;
}
我正试图从存储过程中创建类似于Entities Framework创建复杂类型的东西。我可以劫持这种功能吗?
在这个例子中,Id列.. tblJobId(不是我的命名约定)永远不会为null ..但我选择null为ImNull并且它具有所有相同的属性,因此EF如何确定相应的C#数据类型是否应该是是否可空?
是否有人这样做过..
赞赏我们的想法。
答案 0 :(得分:3)
秘诀是使用Schema Only并填充数据集而不是数据表。现在,datacolumn上的AllowDbNull属性正确显示了返回值的可空状态。
就是这样......
public List<DataColumn> GetColumnInfoFromStoredProcResult(string schema, string storedProcName)
{
//build sql text
var sb = new StringBuilder();
sb.Append("SET FMTONLY OFF; SET FMTONLY ON; \n");//this is how EF4.1 did so I copied..not sure why the repeat
sb.Append(String.Format("exec {0}.{1} ", schema, storedProcName));
var prms = GetStoredProcedureParameters(schema: schema, sprocName: storedProcName);
var count = 1;
foreach (var param in prms)
{
sb.Append(String.Format("{0}=null", param.Name));
if (count < prms.Count)
{
sb.Append(", ");
}
count++;
}
sb.Append("\n SET FMTONLY OFF; SET FMTONLY OFF;");
var ds = new DataSet();
using (var sqlConnection = this.SqlConnection)
{
using (var sqlAdapter = new SqlDataAdapter(sb.ToString(), sqlConnection))
{
if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open();
sqlAdapter.SelectCommand.ExecuteReader(CommandBehavior.SchemaOnly);
sqlConnection.Close();
sqlAdapter.FillSchema(ds, SchemaType.Source, "MyTable");
}
}
var list = ds.Tables[0].Columns.Cast<DataColumn>().ToList();
return list;
}
public List<SqlParamInfo> GetStoredProcedureParameters(string schema, string sprocName)
{
var sqlText = String.Format(
@"SELECT
[Name] = N'@RETURN_VALUE',
[ID] = 0,
[Direction] = 6,
[UserType] = NULL,
[SystemType] = N'int',
[Size] = 4,
[Precision] = 10,
[Scale] = 0
WHERE
OBJECTPROPERTY(OBJECT_ID(N'{0}.{1}'), 'IsProcedure') = 1
UNION
SELECT
[Name] = CASE WHEN p.name <> '' THEN p.name ELSE '@RETURN_VALUE' END,
[ID] = p.parameter_id,
[Direction] = CASE WHEN p.is_output = 0 THEN 1 WHEN p.parameter_id > 0 AND p.is_output = 1 THEN 3 ELSE 6 END,
[UserType] = CASE WHEN ut.is_assembly_type = 1 THEN SCHEMA_NAME(ut.schema_id) + '.' + ut.name ELSE NULL END,
[SystemType] = CASE WHEN ut.is_assembly_type = 0 AND ut.user_type_id = ut.system_type_id THEN ut.name WHEN ut.is_user_defined = 1 OR ut.is_assembly_type = 0 THEN st.name WHEN ut.is_table_type =1 Then 'STRUCTURED' ELSE 'UDT' END,
[Size] = CONVERT(int, CASE WHEN st.name IN (N'text', N'ntext', N'image') AND p.max_length = 16 THEN -1 WHEN st.name IN (N'nchar', N'nvarchar', N'sysname') AND p.max_length >= 0 THEN p.max_length/2 ELSE p.max_length END),
[Precision] = p.precision,
[Scale] = p.scale
FROM
sys.all_parameters p
INNER JOIN sys.types ut ON p.user_type_id = ut.user_type_id
LEFT OUTER JOIN sys.types st ON ut.system_type_id = st.user_type_id AND ut.system_type_id = st.system_type_id
WHERE
object_id = OBJECT_ID(N'{0}.{1}')
ORDER BY 2", schema, sprocName);
using (var sqlConnection = this.SqlConnection)
{
using (var sqlCommand = new SqlCommand())
{
if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open();
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandType = CommandType.Text;
sqlCommand.CommandText = sqlText;
var dr = sqlCommand.ExecuteReader();
var result = new List<SqlParamInfo>();
while (dr.Read())
{
if (Convert.ToString(dr["Name"]) != "@RETURN_VALUE")
{
result.Add(new SqlParamInfo(dr));
}
}
return result;
}
}
}
答案 1 :(得分:0)
假设来自SP的每一列都可以为空 - 这是一个有效的假设,因为存储过程 - 它是一种数据抽象层,因此它的代码可以改变但仍然产生有效的结果。
如果列昨天不可为空,那么今天就没有任何意义。所以 - 来自SP结果集的所有列都可以通过设计为空。
更新。
假设表t1具有列Id INT IDENTITY PRIMARY KEY
您的存储过程如下所示:
CREATE PROC p1
AS
BEGIN
SELECT Id FROM t1
END
所以它永远不会返回Id = NULL,但这是SP - 数据的抽象,所以 - 明天我会修改它:
CREATE PROC p1
AS
BEGIN
SELECT Id FROM t1
UNION
SELECT NULL
END
所以,现在它返回NULL - 想想这个。对数据抽象
的理解不同