Ich schreibe eine C # -Anwendung, um mit Dapper for ORM Rezepte aus einer MySQL-Datenbank abzurufen. Bisher habe ich mein DAL in C # mit direkten Abfragen geschrieben (von denen ich weiß, dass sie unsicher sind), und das funktioniert hervorragend. Ich habe jetzt damit begonnen, auf gespeicherte Prozeduren mit Parametern umzusteigen, um die Datenbank besser vor SQL-Injection zu schützen und so gut wie möglich Best Practices zu verwenden.
Wenn ich jedoch QueryAsync<T>
(dies gilt auch für Query<T>
) zusammen mit DynamicParameters
, wird eine Ausnahme mit der Meldung "Daten sind Null" DynamicParameters
. Diese Methode oder Eigenschaft kann nicht für Nullwerte aufgerufen werden. ""
Wenn ich die Abfrage jedoch entweder als SQL-Anweisung für ein Zeichenfolgenliteral ausführe oder ein Zeichenfolgenliteral zum Aufrufen der gespeicherten Prozedur verwende, funktioniert dies einwandfrei. Ich weiß, dass die Daten vorhanden sind und nicht null, da sie funktionieren, wenn sie direkt in MySQL mit einer mir bekannten ID-Nummer ausgeführt werden. Ich habe auch versucht, die unten in C # aufgeführten Methoden mit einer mir bekannten ID auszuführen. Einige funktionieren einwandfrei, andere geben den angegebenen Fehler zurück.
Ich habe keine Ahnung, wo dies fehlschlägt, wenn ich den QueryAsync<Recipe>("...")
. Ich weiß nicht, ob die Parameter, die ich der Methode zur Verfügung stelle, nicht an die gespeicherte Prozedur übergeben werden oder ob die Prozedur null zurückgibt oder etwas anderes, wenn etwas schief geht.
Jede Hilfe bei der Ermittlung, wo dies bei diesem Anruf fehlschlägt, wäre sehr dankbar. Ich habe den Stack-Trace unten eingefügt, was ich noch nicht verstehen kann. Ich muss noch lernen, Stapelspuren zu verstehen.
Bearbeiten: Ich habe die MySQL-Datenbank in SQL Server neu erstellt und einen neuen DAL-Connector erstellt. Alle spiegeln genau die MySQL-Struktur und DAL wider. GetRecipeByIdAsync1(int id)
funktioniert mit SQL Server genau wie erwartet. Es muss also etwas an der Art und Weise liegen, wie Dapper / DynamicParameters / MySql.Data mit der in MySQL gespeicherten Prozedur interagiert
Meine Rezeptklasse:
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; }
}
Dies ist meine recipes
in MySQL:
recipes
=============
id (pk) | INT | Not Null | Auto-Increment
name | VARCHAR(45) | Not Null |
description | VARCHAR(250) | Allow Null |
source_site | VARCAHR(200) | Allow Null |
Dies ist die Hilfsklasse, mit der ich die benutzerdefinierte Zuordnung festlege, damit meine Spalten nicht mit den Eigenschaftsnamen übereinstimmen müssen:
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
}
Die gespeicherte Prozedur, die ich verwende:
PROCEDURE `sp_recipes_GetByRecipeId`(IN RecipeId INT)
BEGIN
SELECT r.*
FROM recipes r
WHERE r.id = RecipeId;
END
Nun zu den verschiedenen Versionen der Methode, die ich in meinem DAL verwende (ich habe sie hier zur Vereinfachung nummeriert):
/// 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();
}
}
Verbindungszeichenfolge, wenn jemand wollte
<connectionStrings>
<add name="CookbookTest1" connectionString="Server=localhost;Database=cookbook_test1;Uid=vs_dev;Pwd=developer;" providerName="MySql.Data"/>
</connectionStrings>
Stapelspur:
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
Dies sieht aus wie ein Fehler in Oracle MySQL Connector / NET (auch bekannt als MySql.Data
). Es sieht nicht nach einem Fehler aus, den ich in dieser Fehlerdatenbank kenne. Möglicherweise muss es als neue Ausgabe eingereicht werden. ( Fehler 75301 sieht ähnlich aus, aber es ist nicht sofort offensichtlich, dass es sich um dasselbe Problem handelt.)
Ich würde empfehlen, zu MySqlConnector zu wechseln . Es ist eine alternative ADO.NET-Bibliothek für MySQL, die eine hervorragende Kompatibilität mit Dapper aufweist und viele bekannte Fehler in MySQL Connector / NET behebt . MySqlConnector bietet auch echte asynchrone E / A-Unterstützung, die in Connector / NET nicht implementiert ist . Dies ist wichtig, wenn Sie QueryAsync
in Ihrem Code verwenden QueryAsync
.
Wenn Sie weiterhin MySQL Connector / NET von Oracle verwenden möchten, können Sie das Problem möglicherweise CheckParameters=false
indem CheckParameters=false
Ihrer Verbindungszeichenfolge CheckParameters=false
hinzufügen. Beachten Sie, dass dies eine wichtige Änderung Ihres Codes sein kann. wenn Sie die Einstellung auf false gesetzt, haben Sie manuell , um sicherzustellen , dass die zugegebenen Parameter zu jedem CommandType.StoredProcedure
MySqlCommand
sind genau in der gleichen Reihenfolge wie die Datenbank (weil MySql.Data wird sie nicht mehr für Sie reparieren).
Update: Nach dem Betrachten des Connector / NET-Quellcodes scheint Ihre Datenbank einige Daten zu enthalten, die sie nicht erwartet. Erzeugt eine der beiden folgenden Abfragen Zeilen? Wenn ja, welche Werte sind 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;
Welchen MySQL Server verwenden Sie (MySQL, MariaDB, Amazon Aurora) und welche Version?