Je reçois cette exception lorsque j'essaie de créer une SqlBulkCopy à partir d'un DataTable.
Error Message: The given value of type String from the data source cannot be converted to type money of the specified target column.
Target Site: System.Object ConvertValue(System.Object, System.Data.SqlClient._SqlMetaData, Boolean, Boolean ByRef, Boolean ByRef)
Je comprends ce que dit l'erreur, mais comment puis-je obtenir davantage d'informations, telles que la ligne/le champ concerné? Le datatable est peuplé par une tierce partie et peut contenir jusqu'à 200 colonnes et 10 000 lignes. Les colonnes renvoyées dépendent de la demande envoyée à la 3ème partie. Toutes les colonnesdatatablesont de type chaîne. Les colonnes de ma base de données ne sont pas toutes varchar. Par conséquent, avant d'exécuter l'insertion, je formate les valeurs datatable à l'aide du code suivant (le code non important a été supprimé):
//--- create lists to hold the special data type columns
List<DataColumn> IntColumns = new List<DataColumn>();
List<DataColumn> DecimalColumns = new List<DataColumn>();
List<DataColumn> BoolColumns = new List<DataColumn>();
List<DataColumn> DateColumns = new List<DataColumn>();
foreach (DataColumn Column in dtData.Columns)
{
//--- find the field map that tells the system where to put this piece of data from the 3rd party
FieldMap ColumnMap = AllFieldMaps.Find(a => a.SourceFieldID.ToLower() == Column.ColumnName.ToLower());
//--- get the datatype for this field in our system
Type FieldDataType = Nullable.GetUnderlyingType(DestinationType.Property(ColumnMap.DestinationFieldName).PropertyType);
//--- find the field data type and add to respective list
switch (Type.GetTypeCode(FieldDataType))
{
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64: { IntColumns.Add(Column); break; }
case TypeCode.Boolean: { BoolColumns.Add(Column); break; }
case TypeCode.Double:
case TypeCode.Decimal: { DecimalColumns.Add(Column); break; }
case TypeCode.DateTime: { DateColumns.Add(Column); break; }
}
//--- add the mapping for the column on the BulkCopy object
BulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(Column.ColumnName, ColumnMap.DestinationFieldName));
}
//--- loop through all rows and convert the values to data types that match our database's data type for that field
foreach (DataRow dr in dtData.Rows)
{
//--- convert int values
foreach (DataColumn IntCol in IntColumns)
dr[IntCol] = Helpers.CleanNum(dr[IntCol].ToString());
//--- convert decimal values
foreach (DataColumn DecCol in DecimalColumns)
dr[DecCol] = Helpers.CleanDecimal(dr[DecCol].ToString());
//--- convert bool values
foreach (DataColumn BoolCol in BoolColumns)
dr[BoolCol] = Helpers.ConvertStringToBool(dr[BoolCol].ToString());
//--- convert date values
foreach (DataColumn DateCol in DateColumns)
dr[DateCol] = dr[DateCol].ToString().Replace("T", " ");
}
try
{
//--- do bulk insert
BulkCopy.WriteToServer(dtData);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
//--- handles error
//--- this is where I need to find the row & column having an issue
}
Ce code doit formater toutes les valeurs pour leurs champs de destination. Dans le cas de cette erreur, la décimale, la fonction qui nettoie supprimera tout caractère autre que 0-9 ou. (virgule). Ce champ qui génère l'erreur serait nullable dans la base de données.
L'exception de niveau 2 a cette erreur:
Error Message: Failed to convert parameter value from a String to a Decimal.
Target Site: System.Object CoerceValue(System.Object, System.Data.SqlClient.MetaType, Boolean ByRef, Boolean ByRef, Boolean)
et l'exception de niveau 3 a cette erreur:
Error Message: Input string was not in a correct format
Target Site: Void StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)
Est-ce que quelqu'un a des idées à résoudre? ou des idées pour obtenir plus d'informations?
@Corey - Il supprime simplement tous les caractères non valides. Cependant, votre commentaire m'a fait penser à la réponse.
Le problème était que beaucoup de champs de ma base de données sont nuls. Lors de l'utilisation de SqlBulkCopy, une chaîne vide n'est pas insérée en tant que valeur null. Ainsi, dans le cas de mes champs qui ne sont pas varchar (bits, entiers, décimaux, date/heure, etc.), il essayait d'insérer une chaîne vide, ce qui n'est évidemment pas valide pour ce type de données.
La solution a été de modifier ma boucle où je valide les valeurs (répétée pour chaque type de données qui n'est pas une chaîne).
//--- convert decimal values
foreach (DataColumn DecCol in DecimalColumns)
{
if(string.IsNullOrEmpty(dr[DecCol].ToString()))
dr[DecCol] = null; //--- this had to be set to null, not empty
else
dr[DecCol] = Helpers.CleanDecimal(dr[DecCol].ToString());
}
Après avoir effectué les réglages ci-dessus, tout est inséré sans problème.
Pour les personnes qui tombent sur cette question et reçoivent un message d'erreur similaire en ce qui concerne un nvarchar au lieu d'argent:
La valeur donnée de type String de la source de données ne peut pas être convertie en type nvarchar de la colonne cible spécifiée.
Cela pourrait être causé par une colonne trop courte.
Par exemple, si votre colonne est définie comme nvarchar(20)
et que vous avez une chaîne de 40 caractères, vous risquez d'obtenir cette erreur.
Veuillez utiliser SqlBulkCopyColumnMapping.
Exemple:
private void SaveFileToDatabase(string filePath)
{
string strConnection = System.Configuration.ConfigurationManager.ConnectionStrings["MHMRA_TexMedEvsConnectionString"].ConnectionString.ToString();
String excelConnString = String.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=\"Excel 12.0\"", filePath);
//Create Connection to Excel work book
using (OleDbConnection excelConnection = new OleDbConnection(excelConnString))
{
//Create OleDbCommand to fetch data from Excel
using (OleDbCommand cmd = new OleDbCommand("Select * from [Crosswalk$]", excelConnection))
{
excelConnection.Open();
using (OleDbDataReader dReader = cmd.ExecuteReader())
{
using (SqlBulkCopy sqlBulk = new SqlBulkCopy(strConnection))
{
//Give your Destination table name
sqlBulk.DestinationTableName = "PaySrcCrosswalk";
SqlBulkCopyColumnMapping AdmissionPaySrcID=new SqlBulkCopyColumnMapping("AdmissionPaySrcID","AdmissionPaySrcID");
sqlBulk.ColumnMappings.Add(AdmissionPaySrcID);
SqlBulkCopyColumnMapping TMHP_Detail = new SqlBulkCopyColumnMapping("TMHP_Detail", "TMHP_Detail");
sqlBulk.ColumnMappings.Add(TMHP_Detail);
SqlBulkCopyColumnMapping PaySrcType = new SqlBulkCopyColumnMapping("PaySrcType", "PaySrcType");
sqlBulk.ColumnMappings.Add(PaySrcType);
SqlBulkCopyColumnMapping AgencyID = new SqlBulkCopyColumnMapping("AgencyID", "AgencyID");
sqlBulk.ColumnMappings.Add(AgencyID);
SqlBulkCopyColumnMapping CountyCode = new SqlBulkCopyColumnMapping("CountyCode", "CountyCode");
sqlBulk.ColumnMappings.Add(CountyCode);
SqlBulkCopyColumnMapping EntityID = new SqlBulkCopyColumnMapping("EntityID", "EntityID");
sqlBulk.ColumnMappings.Add(EntityID);
sqlBulk.WriteToServer(dReader);
}
}
}
}
}
Puisque je ne crois pas que "Please use..." plus some random code that is unrelated to the question
est une bonne réponse, mais que l'esprit était correct, j'ai décidé de répondre correctement.
Lorsque vous utilisez Sql Bulk Copy, il tente d’aligner vos données d’entrée directement sur les données du serveur. Donc, il faut la table de serveur et effectue une instruction SQL semblable à ceci:
INSERT INTO [schema].[table] (col1, col2, col3) VALUES
Par conséquent, si vous lui donnez les colonnes 1, 3 et 2, MÊME SI vos noms peuvent correspondre (par exemple: col1, col3, col2). Il va insérer comme si:
INSERT INTO [schema].[table] (col1, col2, col3) VALUES
('col1', 'col3', 'col2')
Ce serait un travail supplémentaire et une surcharge pour l'insertion en bloc SQL de devoir déterminer un mappage de colonne. Cela vous permet donc de choisir ... Assurez-vous que votre code et vos colonnes de la table SQL sont dans le même ordre, ou indiquez explicitement l’alignement par nom de colonne.
Par conséquent, si votre problème est un mauvais alignement des colonnes, ce qui est probablement la cause principale de cette erreur, cette réponse est pour vous.
using System.Data;
//...
myDataTable.Columns.Cast<DataColumn>().ToList().ForEach(x =>
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(x.ColumnName, x.ColumnName)));
Cela prendra votre DataTable existant, que vous tentez d'insérer dans votre objet BulkCopy créé, et il va simplement mapper explicitement nom sur nom. Bien sûr, si, pour une raison quelconque, vous avez décidé de nommer vos colonnes DataTable différemment de vos colonnes SQL Server ... c'est à vous.
Assurez-vous que les valeurs de colonne u ajoutées dans la classe d'entité ont également des propriétés get set dans le même ordre que celui de la table cible.
lorsque vous essayez de mapper, la longueur de chaîne Par exemple, TK_NO nvarchar (50), vous devez le mapper sur la même longueur dans le champ de destination