我正在编写一个C#应用程序,使用Dapper for ORM从MySQL数据库检索配方。到目前为止,我已经用C#编写了直接查询的DAL(我知道这是不安全的),而且效果很好。我现在已经开始过渡到带有参数的存储过程,以更好地保护数据库免受SQL注入的侵扰,以及尽可能地使用最佳实践。
但是,当我同时使用Dapper的QueryAsync<T>
(这也适用于Query<T>
)和DynamicParameters
时,出现了异常,消息为“数据为空。此方法或无法对Null值调用属性。”
但是,如果我以字符串文字SQL语句的形式执行查询,或者使用字符串文字来调用存储过程,则可以正常工作。我知道数据在那里,而不是null,因为当在MySQL中以我知道的ID号直接运行它时,它可以工作。我还尝试过使用我知道的id在C#中运行下面列出的方法,其中一些工作正常,其中一些返回所述错误。
我不知道在进行QueryAsync<Recipe>("...")
调用后哪里会失败。我不知道我提供给该方法的参数是否未传递到存储过程中,或者该过程是否返回null,或者如果出错则返回其他信息。
对于解决该呼叫失败的任何帮助,将不胜感激。 我已经在底部包括了堆栈跟踪,到目前为止还没有任何意义。我仍然需要学习了解堆栈跟踪。
编辑:
我已经在SQL Server中重新创建了MySql数据库,并创建了一个新的DAL连接器。所有这些都完全反映了MySql结构和DAL。 GetRecipeByIdAsync1(int id)
与SQL Server完全一样。因此,在Dapper / DynamicParameters / MySql.Data与MySQL中的存储过程进行交互的方式上肯定有一些东西。
我的食谱课:
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
表:
recipes
=============
id (pk) | INT | Not Null | Auto-Increment
name | VARCHAR(45) | Not Null |
description | VARCHAR(250) | Allow Null |
source_site | VARCAHR(200) | Allow Null |
这是我用来设置自定义映射的帮助程序类,因此我的列无需与属性名称匹配:
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
答案 0 :(得分:1)
这看起来像是Oracle的MySQL Connector / NET(也称为MySql.Data
)中的错误。它看起来像我在该错误数据库中不熟悉的任何错误;它可能需要作为新的问题提交。 (Bug 75301看起来很相似,但并不清楚是同一问题。)
我建议切换到MySqlConnector;它是MySQL的备用ADO.NET库,它与Dapper和MySQL Connector / NET中的fixes many known bugs兼容。 MySqlConnector还具有真正的异步I / O支持,在Connector / NET中为not implemented。如果您想在代码中使用QueryAsync
,这将很重要。
更新:查看了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 Server(MySQL,MariaDB,Amazon Aurora)以及哪个版本?