使用 MySqlCommand 执行存储过程时出现 SqlNullValueException
SqlNullValueException when executing a stored procedure with MySqlCommand
我正在编写一个 C# 应用程序来从 MySQL 数据库中检索食谱,使用 Dapper for ORM。到目前为止,我已经用 C# 编写了带有直接查询的 DAL(我知道这是不安全的),而且效果很好。我现在已经开始过渡到带参数的存储过程,以更好地保护数据库免受 SQL 注入,并尽可能使用接近最佳实践。
但是,当我将 Dapper 的 QueryAsync<T>
(这也适用于 Query<T>
)与 DynamicParameters
一起使用时,出现异常,消息为 "Data is Null. This method or property cannot be called on Null values."
但是,如果我将查询作为字符串文字 SQL 语句执行,或者使用字符串文字调用存储过程,则它可以正常工作。我知道数据在那里,而不是空的,因为它在 运行 直接在 MySQL 中使用我知道存在的设置 ID 号时有效。我还尝试了 运行 下面在 C# 中列出的方法,其中有一个我知道存在的 id,其中一些工作正常,其中一些 return 声明的错误。
我在调用 QueryAsync<Recipe>("...")
后不知道哪里失败了。我不知道我提供给该方法的参数是否没有传递到存储过程中,或者该过程是否为 returning null,或者是否出现其他问题。
如果您能帮助找出该调用可能失败的地方,我们将不胜感激。
我在底部包含了堆栈跟踪,但我目前还无法理解。我仍然需要学习理解堆栈跟踪。
编辑:
我在 SQL 服务器中重新创建了 MySql 数据库,并创建了一个新的 DAL 连接器。所有这些都完全反映了 MySql 结构和 DAL。 GetRecipeByIdAsync1(int id)
使用 SQL 服务器时完全符合预期。所以一定有一些关于 Dapper/DynamicParameters/MySql 的方式。数据与 MySQL
中的存储过程进行交互
我的食谱class:
public class Recipe
{
[Description("id")]
public int Id { get; set; }
[Description("name")]
public string Title { get; set; }
[Description("description")]
public string Description { get; set; }
[Description("source_site")]
public string SourceSite { get; set; }
}
这是我在 MySQL 中的 recipes
table:
recipes
=============
id (pk) | INT | Not Null | Auto-Increment
name | VARCHAR(45) | Not Null |
description | VARCHAR(250) | Allow Null |
source_site | VARCAHR(200) | Allow Null |
这是我用来设置自定义映射的助手 class,因此我的列不需要匹配 属性 名称:
public class Helper
{
public static void SetTypeMaps()
{
var recipeMap = new CustomPropertyTypeMap(typeof(Recipe),
(type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
SqlMapper.SetTypeMap(typeof(Recipe), recipeMap);
// Other custom mappers omitted
}
我正在使用的存储过程:
PROCEDURE `sp_recipes_GetByRecipeId`(IN RecipeId INT)
BEGIN
SELECT r.*
FROM recipes r
WHERE r.id = RecipeId;
END
现在介绍我在 DAL 中使用的方法的各种版本(为方便起见,我在此处对它们进行了编号):
/// This does not work
public async Task<Recipe> GetRecipeByIdAsync1(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
var p = new DynamicParameters();
p.Add("RecipeId", id, dbType: DbType.Int32, direction: ParameterDirection.Input);
// This is the line where the exception occurs
var result = await db.QueryAsync<Recipe>("sp_recipes_GetByRecipeId", p, commandType: CommandType.StoredProcedure);
return result.FirstOrDefault();
}
}
// This also does not work
public async Task<Recipe> GetRecipeByIdAsync2(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
// This is the line where the exception occurs
var result = await db.QueryAsync<Recipe>("sp_recipes_GetByRecipeId", new {RecipeID = id}, commandType: CommandType.StoredProcedure);
return result.FirstOrDefault();
}
}
// Nor this
public async Task<Recipe> GetRecipeByIdAsync3(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
// This is the line where the exception occurs
var result = await db.QueryAsync<Recipe>("sp_recipes_GetByRecipeId", new {id}, commandType: CommandType.StoredProcedure);
return result.FirstOrDefault();
}
}
// This works perfectly, but I'm not sure how safe it is
public async Task<Recipe> GetRecipeByIdAsync4(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
var result = await db.QueryAsync<Recipe>($"call sp_recipes_GetByRecipeId({id})");
return result.FirstOrDefault();
}
}
// And of course, this works, but is horrible practice
public async Task<Recipe> GetRecipeByIdAsync5(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
var result = await db.QueryAsync<Recipe>($"SELECT * FROM recipes WHERE recipes.id = {id}");
return result.FirstOrDefault();
}
}
连接字符串(如果有人需要)
<connectionStrings>
<add name="CookbookTest1" connectionString="Server=localhost;Database=cookbook_test1;Uid=vs_dev;Pwd=developer;" providerName="MySql.Data"/>
</connectionStrings>
堆栈跟踪:
System.Data.SqlTypes.SqlNullValueException
HResult=0x80131931
Message=Data is Null. This method or property cannot be called on Null values.
Source=MySql.Data
StackTrace:
at MySql.Data.MySqlClient.MySqlDataReader.GetFieldValue(Int32 index, Boolean checkNull)
at MySql.Data.MySqlClient.MySqlDataReader.GetString(Int32 i)
at MySql.Data.MySqlClient.MySqlDataReader.GetString(String column)
at MySql.Data.MySqlClient.SchemaProvider.GetProcedures(String[] restrictions)
at MySql.Data.MySqlClient.ISSchemaProvider.GetProcedures(String[] restrictions)
at MySql.Data.MySqlClient.ISSchemaProvider.GetSchemaInternal(String collection, String[] restrictions)
at MySql.Data.MySqlClient.SchemaProvider.GetSchema(String collection, String[] restrictions)
at MySql.Data.MySqlClient.MySqlConnection.GetSchemaCollection(String collectionName, String[] restrictionValues)
at MySql.Data.MySqlClient.ProcedureCache.GetProcData(MySqlConnection connection, String spName)
at MySql.Data.MySqlClient.ProcedureCache.AddNew(MySqlConnection connection, String spName)
at MySql.Data.MySqlClient.ProcedureCache.GetProcedure(MySqlConnection conn, String spName, String cacheKey)
at MySql.Data.MySqlClient.StoredProcedure.GetParameters(String procName)
at MySql.Data.MySqlClient.StoredProcedure.CheckParameters(String spName)
at MySql.Data.MySqlClient.StoredProcedure.Resolve(Boolean preparing)
at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior)
at MySql.Data.MySqlClient.MySqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at Dapper.SqlMapper.<QueryAsync>d__33`1.MoveNext() in C:\projects\dapper\Dapper\SqlMapper.Async.cs:line 468
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at CookbookLibrary.DataAccess.MySqlConnector.<TestStoredProcAsync>d__5.MoveNext() in C:\Users\cyclone\Desktop\VS Projects\DigitalCookbook\CookbookLibrary\DataAccess\MySqlConnector.cs:line 119
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at DigitalCookbook.ViewModel.MainWindowModel.<TestProcedure>d__38.MoveNext() in C:\Users\cyclone\Desktop\VS Projects\DigitalCookbook\DigitalCookbook\ViewModel\MainWindowModel.cs:line 228
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at DigitalCookbook.ViewModel.MainWindowModel.<<get_TestCommand>b__31_0>d.MoveNext() in C:\Users\cyclone\Desktop\VS Projects\DigitalCookbook\DigitalCookbook\ViewModel\MainWindowModel.cs:line 114
这看起来像是 Oracle MySQL Connector/NET(又名 MySql.Data
)中的错误。它看起来不像我在该错误数据库中熟悉的任何错误;它可能需要作为新问题提交。 (Bug 75301 看起来很相似,但不是很明显是同一个问题。)
我建议在 Connector/NET 中切换到 MySqlConnector; it's an alternate ADO.NET library for MySQL that has great compatibility with Dapper and fixes many known bugs in MySQL Connector/NET. MySqlConnector also has true async I/O support, which is not implemented;如果您想在代码中使用 QueryAsync
,这将很重要。
如果您想继续使用 Oracle 的 MySQL Connector/NET,您可以通过将 CheckParameters=false
添加到您的连接字符串来解决该问题。请注意,这可能是对您的代码的重大更改;如果将设置设置为 false,则必须手动确保添加到每个 CommandType.StoredProcedure
MySqlCommand
的参数与数据库的顺序完全相同(因为 MySql.Data 将不再为您修好它们)。
更新: 查看 Connector/NET 源代码后,您的数据库似乎有一些它不期望的数据。以下两个查询中的任何一个都会产生行吗?如果是,哪些值是 NULL
?
SELECT * FROM information_schema.routines
WHERE specific_name IS NULL OR
routine_schema IS NULL OR
routine_name IS NULL OR
routine_type IS NULL OR
routine_definition IS NULL OR
is_deterministic IS NULL OR
sql_data_access IS NULL OR
security_type IS NULL OR
sql_mode IS NULL OR
routine_comment IS NULL OR
definer IS NULL;
SELECT * FROM mysql.proc
WHERE specific_name IS NULL OR
db IS NULL OR
name IS NULL OR
type IS NULL OR
body IS NULL OR
is_deterministic IS NULL OR
sql_data_access IS NULL OR
security_type IS NULL OR
sql_mode IS NULL OR
comment IS NULL OR
definer IS NULL;
您使用什么 MySQL 服务器(MySQL、MariaDB、Amazon Aurora)和哪个版本?
我正在编写一个 C# 应用程序来从 MySQL 数据库中检索食谱,使用 Dapper for ORM。到目前为止,我已经用 C# 编写了带有直接查询的 DAL(我知道这是不安全的),而且效果很好。我现在已经开始过渡到带参数的存储过程,以更好地保护数据库免受 SQL 注入,并尽可能使用接近最佳实践。
但是,当我将 Dapper 的 QueryAsync<T>
(这也适用于 Query<T>
)与 DynamicParameters
一起使用时,出现异常,消息为 "Data is Null. This method or property cannot be called on Null values."
但是,如果我将查询作为字符串文字 SQL 语句执行,或者使用字符串文字调用存储过程,则它可以正常工作。我知道数据在那里,而不是空的,因为它在 运行 直接在 MySQL 中使用我知道存在的设置 ID 号时有效。我还尝试了 运行 下面在 C# 中列出的方法,其中有一个我知道存在的 id,其中一些工作正常,其中一些 return 声明的错误。
我在调用 QueryAsync<Recipe>("...")
后不知道哪里失败了。我不知道我提供给该方法的参数是否没有传递到存储过程中,或者该过程是否为 returning null,或者是否出现其他问题。
如果您能帮助找出该调用可能失败的地方,我们将不胜感激。 我在底部包含了堆栈跟踪,但我目前还无法理解。我仍然需要学习理解堆栈跟踪。
编辑:
我在 SQL 服务器中重新创建了 MySql 数据库,并创建了一个新的 DAL 连接器。所有这些都完全反映了 MySql 结构和 DAL。 GetRecipeByIdAsync1(int id)
使用 SQL 服务器时完全符合预期。所以一定有一些关于 Dapper/DynamicParameters/MySql 的方式。数据与 MySQL
我的食谱class:
public class Recipe
{
[Description("id")]
public int Id { get; set; }
[Description("name")]
public string Title { get; set; }
[Description("description")]
public string Description { get; set; }
[Description("source_site")]
public string SourceSite { get; set; }
}
这是我在 MySQL 中的 recipes
table:
recipes
=============
id (pk) | INT | Not Null | Auto-Increment
name | VARCHAR(45) | Not Null |
description | VARCHAR(250) | Allow Null |
source_site | VARCAHR(200) | Allow Null |
这是我用来设置自定义映射的助手 class,因此我的列不需要匹配 属性 名称:
public class Helper
{
public static void SetTypeMaps()
{
var recipeMap = new CustomPropertyTypeMap(typeof(Recipe),
(type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
SqlMapper.SetTypeMap(typeof(Recipe), recipeMap);
// Other custom mappers omitted
}
我正在使用的存储过程:
PROCEDURE `sp_recipes_GetByRecipeId`(IN RecipeId INT)
BEGIN
SELECT r.*
FROM recipes r
WHERE r.id = RecipeId;
END
现在介绍我在 DAL 中使用的方法的各种版本(为方便起见,我在此处对它们进行了编号):
/// This does not work
public async Task<Recipe> GetRecipeByIdAsync1(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
var p = new DynamicParameters();
p.Add("RecipeId", id, dbType: DbType.Int32, direction: ParameterDirection.Input);
// This is the line where the exception occurs
var result = await db.QueryAsync<Recipe>("sp_recipes_GetByRecipeId", p, commandType: CommandType.StoredProcedure);
return result.FirstOrDefault();
}
}
// This also does not work
public async Task<Recipe> GetRecipeByIdAsync2(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
// This is the line where the exception occurs
var result = await db.QueryAsync<Recipe>("sp_recipes_GetByRecipeId", new {RecipeID = id}, commandType: CommandType.StoredProcedure);
return result.FirstOrDefault();
}
}
// Nor this
public async Task<Recipe> GetRecipeByIdAsync3(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
// This is the line where the exception occurs
var result = await db.QueryAsync<Recipe>("sp_recipes_GetByRecipeId", new {id}, commandType: CommandType.StoredProcedure);
return result.FirstOrDefault();
}
}
// This works perfectly, but I'm not sure how safe it is
public async Task<Recipe> GetRecipeByIdAsync4(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
var result = await db.QueryAsync<Recipe>($"call sp_recipes_GetByRecipeId({id})");
return result.FirstOrDefault();
}
}
// And of course, this works, but is horrible practice
public async Task<Recipe> GetRecipeByIdAsync5(int id)
{
using (IDbConnection db = new MySqlConnection(GlobalConfig.CnnString("CookbookTest1")))
{
var result = await db.QueryAsync<Recipe>($"SELECT * FROM recipes WHERE recipes.id = {id}");
return result.FirstOrDefault();
}
}
连接字符串(如果有人需要)
<connectionStrings>
<add name="CookbookTest1" connectionString="Server=localhost;Database=cookbook_test1;Uid=vs_dev;Pwd=developer;" providerName="MySql.Data"/>
</connectionStrings>
堆栈跟踪:
System.Data.SqlTypes.SqlNullValueException
HResult=0x80131931
Message=Data is Null. This method or property cannot be called on Null values.
Source=MySql.Data
StackTrace:
at MySql.Data.MySqlClient.MySqlDataReader.GetFieldValue(Int32 index, Boolean checkNull)
at MySql.Data.MySqlClient.MySqlDataReader.GetString(Int32 i)
at MySql.Data.MySqlClient.MySqlDataReader.GetString(String column)
at MySql.Data.MySqlClient.SchemaProvider.GetProcedures(String[] restrictions)
at MySql.Data.MySqlClient.ISSchemaProvider.GetProcedures(String[] restrictions)
at MySql.Data.MySqlClient.ISSchemaProvider.GetSchemaInternal(String collection, String[] restrictions)
at MySql.Data.MySqlClient.SchemaProvider.GetSchema(String collection, String[] restrictions)
at MySql.Data.MySqlClient.MySqlConnection.GetSchemaCollection(String collectionName, String[] restrictionValues)
at MySql.Data.MySqlClient.ProcedureCache.GetProcData(MySqlConnection connection, String spName)
at MySql.Data.MySqlClient.ProcedureCache.AddNew(MySqlConnection connection, String spName)
at MySql.Data.MySqlClient.ProcedureCache.GetProcedure(MySqlConnection conn, String spName, String cacheKey)
at MySql.Data.MySqlClient.StoredProcedure.GetParameters(String procName)
at MySql.Data.MySqlClient.StoredProcedure.CheckParameters(String spName)
at MySql.Data.MySqlClient.StoredProcedure.Resolve(Boolean preparing)
at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior)
at MySql.Data.MySqlClient.MySqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at Dapper.SqlMapper.<QueryAsync>d__33`1.MoveNext() in C:\projects\dapper\Dapper\SqlMapper.Async.cs:line 468
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at CookbookLibrary.DataAccess.MySqlConnector.<TestStoredProcAsync>d__5.MoveNext() in C:\Users\cyclone\Desktop\VS Projects\DigitalCookbook\CookbookLibrary\DataAccess\MySqlConnector.cs:line 119
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at DigitalCookbook.ViewModel.MainWindowModel.<TestProcedure>d__38.MoveNext() in C:\Users\cyclone\Desktop\VS Projects\DigitalCookbook\DigitalCookbook\ViewModel\MainWindowModel.cs:line 228
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at DigitalCookbook.ViewModel.MainWindowModel.<<get_TestCommand>b__31_0>d.MoveNext() in C:\Users\cyclone\Desktop\VS Projects\DigitalCookbook\DigitalCookbook\ViewModel\MainWindowModel.cs:line 114
这看起来像是 Oracle MySQL Connector/NET(又名 MySql.Data
)中的错误。它看起来不像我在该错误数据库中熟悉的任何错误;它可能需要作为新问题提交。 (Bug 75301 看起来很相似,但不是很明显是同一个问题。)
我建议在 Connector/NET 中切换到 MySqlConnector; it's an alternate ADO.NET library for MySQL that has great compatibility with Dapper and fixes many known bugs in MySQL Connector/NET. MySqlConnector also has true async I/O support, which is not implemented;如果您想在代码中使用 QueryAsync
,这将很重要。
如果您想继续使用 Oracle 的 MySQL Connector/NET,您可以通过将 CheckParameters=false
添加到您的连接字符串来解决该问题。请注意,这可能是对您的代码的重大更改;如果将设置设置为 false,则必须手动确保添加到每个 CommandType.StoredProcedure
MySqlCommand
的参数与数据库的顺序完全相同(因为 MySql.Data 将不再为您修好它们)。
更新: 查看 Connector/NET 源代码后,您的数据库似乎有一些它不期望的数据。以下两个查询中的任何一个都会产生行吗?如果是,哪些值是 NULL
?
SELECT * FROM information_schema.routines
WHERE specific_name IS NULL OR
routine_schema IS NULL OR
routine_name IS NULL OR
routine_type IS NULL OR
routine_definition IS NULL OR
is_deterministic IS NULL OR
sql_data_access IS NULL OR
security_type IS NULL OR
sql_mode IS NULL OR
routine_comment IS NULL OR
definer IS NULL;
SELECT * FROM mysql.proc
WHERE specific_name IS NULL OR
db IS NULL OR
name IS NULL OR
type IS NULL OR
body IS NULL OR
is_deterministic IS NULL OR
sql_data_access IS NULL OR
security_type IS NULL OR
sql_mode IS NULL OR
comment IS NULL OR
definer IS NULL;
您使用什么 MySQL 服务器(MySQL、MariaDB、Amazon Aurora)和哪个版本?