SqlBulkCopy Not Working - c#

I have a DataSet populated from Excel Sheet. I wanted to use SQLBulk Copy to Insert Records in Lead_Hdr table where LeadId is PK.
I am having following error while executing the code below:
The given ColumnMapping does not match up with any column in the
source or destination
string ConStr=ConfigurationManager.ConnectionStrings["ConStr"].ToString();
using (SqlBulkCopy s = new SqlBulkCopy(ConStr,SqlBulkCopyOptions.KeepIdentity))
{
if (MySql.State==ConnectionState.Closed)
{
MySql.Open();
}
s.DestinationTableName = "PCRM_Lead_Hdr";
s.NotifyAfter = 10000;
#region Comment
s.ColumnMappings.Clear();
#region ColumnMapping
s.ColumnMappings.Add("ClientID", "ClientID");
s.ColumnMappings.Add("LeadID", "LeadID");
s.ColumnMappings.Add("Company_Name", "Company_Name");
s.ColumnMappings.Add("Website", "Website");
s.ColumnMappings.Add("EmployeeCount", "EmployeeCount");
s.ColumnMappings.Add("Revenue", "Revenue");
s.ColumnMappings.Add("Address", "Address");
s.ColumnMappings.Add("City", "City");
s.ColumnMappings.Add("State", "State");
s.ColumnMappings.Add("ZipCode", "ZipCode");
s.ColumnMappings.Add("CountryId", "CountryId");
s.ColumnMappings.Add("Phone", "Phone");
s.ColumnMappings.Add("Fax", "Fax");
s.ColumnMappings.Add("TimeZone", "TimeZone");
s.ColumnMappings.Add("SicNo", "SicNo");
s.ColumnMappings.Add("SicDesc", "SicDesc");
s.ColumnMappings.Add("SourceID", "SourceID");
s.ColumnMappings.Add("ResearchAnalysis", "ResearchAnalysis");
s.ColumnMappings.Add("BasketID", "BasketID");
s.ColumnMappings.Add("PipeLineStatusId", "PipeLineStatusId");
s.ColumnMappings.Add("SurveyId", "SurveyId");
s.ColumnMappings.Add("NextCallDate", "NextCallDate");
s.ColumnMappings.Add("CurrentRecStatus", "CurrentRecStatus");
s.ColumnMappings.Add("AssignedUserId", "AssignedUserId");
s.ColumnMappings.Add("AssignedDate", "AssignedDate");
s.ColumnMappings.Add("ToValueAmt", "ToValueAmt");
s.ColumnMappings.Add("Remove", "Remove");
s.ColumnMappings.Add("Release", "Release");
s.ColumnMappings.Add("Insert_Date", "Insert_Date");
s.ColumnMappings.Add("Insert_By", "Insert_By");
s.ColumnMappings.Add("Updated_Date", "Updated_Date");
s.ColumnMappings.Add("Updated_By", "Updated_By");
#endregion
#endregion
s.WriteToServer(sourceTable);
s.Close();
MySql.Close();
}

I've encountered the same problem while copying data from access to SQLSERVER 2005 and i found that the column mappings are case sensitive on both data sources regardless of the databases sensitivity.

Well, is it right? Do the column names exist on both sides?
To be honest, I've never bothered with mappings. I like to keep things simple - I tend to have a staging table that looks like the input on the server, then I SqlBulkCopy into the staging table, and finally run a stored procedure to move the table from the staging table into the actual table; advantages:
no issues with live data corruption if the import fails at any point
I can put a transaction just around the SPROC
I can have the bcp work without logging, safe in the knowledge that the SPROC will be logged
it is simple ;-p (no messing with mappings)
As a final thought - if you are dealing with bulk data, you can get better throughput using IDataReader (since this is a streaming API, where-as DataTable is a buffered API). For example, I tend to hook CSV imports up using CsvReader as the source for a SqlBulkCopy. Alternatively, I have written shims around XmlReader to present each first-level element as a row in an IDataReader - very fast.

The answer by Marc would be my recomendation (on using staging table). This ensures that if your source doesn't change, you'll have fewer issues importing in the future.
However, in my experience, you can check the following issues:
Column names match in source and table
That the column types match
If you think you did this and still no success. You can try the following.
1 - Allow nulls in all columns in your table
2 - comment out all column mappings
3 - rerun adding one column at a time until you find where your issue is
That should bring out the bug

One of the reason is that :SqlBukCOpy is case sensitive . Follow steps:
In that Case first you have to find your column in Source Table by
using "Contain" method in C#.
Once your Destination column matched with source column get index of
that column and give its column name in SqlBukCOpy .
For Example:`
//Get Column from Source table
string sourceTableQuery = "Select top 1 * from sourceTable";
DataTable dtSource=SQLHelper.SqlHelper.ExecuteDataset(transaction, CommandType.Text, sourceTableQuery).Tables[0];// i use sql helper for executing query you can use corde sw
for (int i = 0; i < destinationTable.Columns.Count; i++)
{ //check if destination Column Exists in Source table
if (dtSource.Columns.Contains(destinationTable.Columns[i].ToString()))//contain method is not case sensitive
{
int sourceColumnIndex = dtSource.Columns.IndexOf(destinationTable.Columns[i].ToString());//Once column matched get its index
bulkCopy.ColumnMappings.Add(dtSource.Columns[sourceColumnIndex].ToString(), dtSource.Columns[sourceColumnIndex].ToString());//give coluns name of source table rather then destination table so that it would avoid case sensitivity
}
}
bulkCopy.WriteToServer(destinationTable);
bulkCopy.Close();

I would go with the staging idea, however here is my approach to handling the case sensitive nature. Happy to be critiqued on my linq
using (SqlConnection connection = new SqlConnection(conn_str))
{
connection.Open();
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName = string.Format("[{0}].[{1}].[{2}]", targetDatabase, targetSchema, targetTable);
var targetColumsAvailable = GetSchema(conn_str, targetTable).ToArray();
foreach (var column in dt.Columns)
{
if (targetColumsAvailable.Select(x => x.ToUpper()).Contains(column.ToString().ToUpper()))
{
var tc = targetColumsAvailable.Single(x => String.Equals(x, column.ToString(), StringComparison.CurrentCultureIgnoreCase));
bulkCopy.ColumnMappings.Add(column.ToString(), tc);
}
}
// Write from the source to the destination.
bulkCopy.WriteToServer(dt);
bulkCopy.Close();
}
}
and the helper method
private static IEnumerable<string> GetSchema(string connectionString, string tableName)
{
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "sp_Columns";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#table_name", SqlDbType.NVarChar, 384).Value = tableName;
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return (string)reader["column_name"];
}
}
}
}

What I have found is that the columns in the table and the columns in the input must at least match. You can have more columns in the table and the input will still load. If you have less you'll receive the error.

Thought a long time about answering...
Even if column names are case equally, if the data type differs
you get the same error. So check column names and their data type.
P.S.: staging tables are definitive the way to import.

Related

SQL Bulk Copy "The given value of type String from the data source cannot be converted to type datetime of the specified target column" using ASP.NET

I'm working on an ASP.NET MVC4 projet and I'm trying to export data from an xlsx file (Excel 2010 file) to my database by using SQL Bulk Copy. My Excel file contains only 2 columns : the first contains numbers (from 1 to 25) and the second contains characters (successive series of "a, b, c")
This is how I try to do in order to export data but I got the error "The given value of type String from the data source cannot be converted to type int of the specified target column" :
public ActionResult Bulk()
{
string xConnStr = #"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\maarab\Documents\BulkCopy.xlsx;Extended Properties=Excel 12.0;";
using (OleDbConnection connection = new OleDbConnection(xConnStr))
{
OleDbCommand command = new OleDbCommand("Select * FROM [Sheet1$]", connection);
connection.Open();
string dbConnection = ((EntityConnection)db.Connection).StoreConnection.ConnectionString;
// Create DbDataReader to Data Worksheet
using (DbDataReader dr = command.ExecuteReader())
{
using (var bulkCopy = new SqlBulkCopy(dbConnection))
{
bulkCopy.DestinationTableName = "bm_test"
bulkCopy.WriteToServer(dr); //here I got the error
}
}
return RedirectToAction("Index");
}
Any idea about what's causing this error?
SqlBulkCopy.WriteToServer(DataTable) fails with confusing messages if the column order of the DataTable differs from the column order of the table definition in your database (when this causes a type or length incompatibility). Apparently the WriteToServer method does not map column names.
Mine was whining about this until I set the orders manually like so:
SqlBulkCopy sbc = new SqlBulkCopy(ConnectionString, SqlBulkCopyOptions.UseInternalTransaction);
sbc.DestinationTableName = "agSoilShapes";
sbc.ColumnMappings.Add("Mukey", "Mukey");
sbc.ColumnMappings.Add("Musym", "Musym");
sbc.ColumnMappings.Add("Shapes", "Shapes");
DataTable dt = new DataTable();
dt.Columns.Add("Mukey", typeof(SqlInt32));
dt.Columns.Add("Musym", typeof(SqlString));
dt.Columns.Add("Shapes", typeof(SqlGeometry));
Thanks to others (#subsci) for comments leading me in this direction :)
I have received a similar error when using EntityFramework.BulkInsert package. In this case the underlying cause was an order mismatch between database table columns and generated model metadata columns (database-first).
DataTable Columns order and Database tables' columns order should be the same. It worked after updating order of tables columns.

.NET MySQL Data Reader Select Function?

It has been a long time since I have used .NET, but thankfully have almost finished writing a tool to compare an sqlite and mysql database. I am running into an issue though when trying to write a function for my wrapper that will handle SELECT calls as I cannot entirely figure out the Data Reader.
My understanding is that each iteration of a loop on the reader is the next row, and GetString(x) returns the column value for "x" as the index. All the examples I found though went into it knowing the row/column names they needed. How can I do this with a "SELECT * FROM" call and save the column names/values for later access? Microsoft seems to have a "FieldCount" function but I am coming up empty on the MySQL Connector.
Thanks!
public void Query(string query, string tableName)
{
//Open connection
if (this.OpenConnection() == true)
{
//Create Command
MySqlCommand cmd = new MySqlCommand(query, connection);
MySqlDataReader dataReader = cmd.ExecuteReader();
//Read the data and store them in the list
while (dataReader.Read())
{
int count = Count(tableName);
for (int x = 0; x < count; x++)
{
Console.WriteLine(dataReader.GetString(count));
}
}
//close Data Reader
dataReader.Close();
//close Connection
this.CloseConnection();
}
}
You can use DbDataReader.GetName to get the name of a column given its ordinal x.
use the "mysql connector" to access data in MySql it is more simple then to write the queries by your self:
http://dev.mysql.com/downloads/connector/net/
Then use the EntityFramework to access the data through this connector. Also you can automaticly generate *.edmx model from your existing DB in mysql, this will let you to fastly access and work with the Data in your database. Here is information about adding *.edmx model from existing DB:
http://msdn.microsoft.com/en-us/library/vstudio/cc716703(v=vs.100).aspx
The query which will select all data from the table, for example - "Products" will look like that:
List products = dbContext.Products.Where(e=>e.ProductId!=-1).ToList();
this will return the whole list of products in your data base in table Products.
then you can work with products as you want. for example taking the "Name" column for the first product in 'products' will look like that:
String firstProductName = products[0].name;
I hope it helps.

How to get table name of a column from SqlDataReader

I have an SQL query I get from a configuration file, this query usually contains 3-6 joins.
I need to find at run time, based on the result set represented by SqlDataReader, to find the name of the table for each column.
Here are some thing that don't work:
SqlDataReader.GetName returns the column name but not the table name.
SqlDataReader.GetSchemaTable returns a data table with column information - but all the table names are null.
Querying information_schema doesn't help because I need data on the results of the current query (and the column names are not unique - there are columns with the same name in different tables).
I'm using .net 3.5SP1/ C#/ SQL Server 2008 in a console application.
EDIT: I know this is not possible for all cases since a "column" can be combined from multiple tables, a function or even a constant expression - I'm looking for something that works in the simple case.
EDIT 2: Found out why it didn't work - You can use SqlDataReader.GetSchemaTable to get table information but you have to set CommandBehavior to KeyInfo, you do that in the ExecuteReader call:
reader = cmd.ExecuteReader(CommandBehavior.KeyInfo);
You can use SqlDataReader.GetSchemaTable to get table information but you have to set CommandBehavior to KeyInfo, you do that in the ExecuteReader call:
reader = cmd.ExecuteReader(CommandBehavior.KeyInfo);
This unanswered question on stackoverflow uses SqlDataReader.GetSchemaTable to get the table name. Their problem is that it returns the actual table name rather than the alias that the table has. Not sure if this works with your sql but figured I'd let you know just in case.
I don't know if this information is available. In particular, not all columns of a result set come from a table. From a relational point of view, tables and resultsets are the same thing.
reader = cmd.ExecuteReader();
reader.GetSchemaTable().Rows[0]["BaseTableName"];
In general, this is not possible. Consider the following query:
SELECT col1 FROM table1
UNION ALL
SELECT col1 FROM table2
Clearly col1 comes from more than one table.
you can solve it like the following :
DataTable schemaTable = sqlReader.GetSchemaTable();
foreach (DataRow row in schemaTable.Rows)
{
foreach (DataColumn column in schemaTable.Columns)
{
MessageBox.Show (string.Format("{0} = {1}", column.ColumnName, row[column]));
}
}
SqlCeConnection conn = new SqlCeConnection("Data Source = Database1.sdf");
SqlCeCommand query = conn.CreateCommand();
query.CommandText = "myTableName";
query.CommandType = CommandType.TableDirect;
conn.Open();
SqlCeDataReader myreader = query.ExecuteReader(CommandBehavior.KeyInfo);
DataTable myDataTable= myreader.GetSchemaTable();
//thats the code you asked. in the loop
for (int i = 0; i < myDataTable.Rows.Count; i++)
{
listView1.Columns.Add(myDataTable.Rows[i][0].ToString());
}
How to get database name, table name & column name.
Also possible to get Schema name as well. Tested with MS SQL 2016
CommandBehavior.KeyInfo must be indicated
SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(CommandBehavior.KeyInfo);
DataTable dataTable = sqlDataReader.GetSchemaTable();
for (int i = 0; i < dataTable.Rows.Count - 1; i++)
{
string ii =
dataTable.Rows[i]["BaseCatalogName"].ToString() + "\\" +
dataTable.Rows[i]["BaseTableName"].ToString() + "\\" +
dataTable.Rows[i]["ColumnName"].ToString();
}

how to compare elements in a string with the database table values

In my project i have to give a string input through a text field, and i have to fill a database table with these values. I should first check the values of a specific table column, and add the input string only if it is not there in the table already.
I tried to convert the table values to a string array, but it wasn,t possible.
If anyone have an idea about this, your reply will be really valuable.
Thankx in advance.
Since you say your strings in the database table must be unique, just put a unique index on that field and let the database handle the problem.
CREATE UNIQUE INDEX UIX_YourTableName_YourFieldName
ON dbo.YourTableName(YourFieldName)
Whenever you will try to insert another row with the same string, SQL Server (or any other decent RDBMS) will throw an exception and not insert the value. Problem solved.
If you need to handle the error on the front-end GUI already, you'll need to load the existing entries from your database, using whatever technology you're familiar with, e.g. in ADO.NET (C#, SQL Server) you could do something like:
public List<string> FindExistingValues()
{
List<string> results = new List<string>();
string getStringsCmd = "SELECT (YourFieldName) FROM dbo.YourTableName";
using(SqlConnection _con = new SqlConnection("your connection string here"))
using(SqlCommand _cmd = new SqlCommand(getStringsCmd, _con)
{
_con.Open();
using(SqlDataReader rdr = _con.ExecuteReader())
{
while(rdr.Read())
{
results.Add(rdr.GetString(0));
}
rdr.Close();
}
_con.Close();
}
return results;
}
You would get back a List<string> from that method and then you could check in your UI whether a given string already exists in the list:
List<string> existing = FindExistingValues();
if(!existing.Contains(yournewstring))
{
// store the new value to the database
}
Or third option: you could write a stored procedure that will handle the storing of your new string. Inside it, first check to see whether the string already exists in the database
IF NOT EXISTS(SELECT * FROM dbo.YourTableName WHERE YourFieldName = '(your new string)')
INSERT INTO dbo.YourTableName(YourFieldName) VALUES(your-new-string-here)
and if not, insert it - you'll just need to find a strategy how to deal with the cases where the new string being passed in did indeed already exist (ignore it, or report back an error of some sorts).
Lots of options - up to you which one works best in your scenario!

How to save a table(DataTable) into a single cell in a database?

Is it possible to save a DataTable into SQL database in one cell of type binary for example and read it back again into a DataTable?
I would create, if possible, a xml field inside the sql database and save the datatable as xml
XML Support in Microsoft SQL Server 2005
and
C# and Vb.net example for XML data type tips in SQL Server 2005
should help you
another example took from here
protected bool LoadXml(SqlConnection cn, XmlDocument doc)
{
//Reading the xml from the database
string sql = #"SELECT Id, XmlField FROM TABLE_WITH_XML_FIELD WHERE Id = #Id";
SqlCommand cm = new SqlCommand(sql, cn);
cm.Parameters.Add(new SqlParameter("#Id",1));
using (SqlDataReader dr = cm.ExecuteReader())
{
if (dr.Read())
{
SqlXml MyXml= dr.GetSqlXml(dr.GetOrdinal("XmlField"));
doc.LoadXml( MyXml.Value);
return true;
}
else
{
return false;
}
}
}
why on earth would you want to?
If this is an operation you are going to do more than once, just save it out to a new sql table and read from the table into your DataTable later.
It just breaks normalization rules- the value in the cell is not atomic.
I break that rule myself all the time, but still, it's important to understand alternative approaches.
You could have a related table to store their answers instead of storing all their answer values in a single cell.
Regarding your comment below, you can still do it with a related table. Just use a three-column table: tableID, fieldID, value. Each tableID can have its own set of fieldIDs. The trade-off is with the value datatype- it needs to be a string, which means you don't get the advantages of date or numeric data type enforcement on your back end.

Categories