8

I'm trying to do a bulk insert with the SqlBulkCopy class from flatfiles created by the SQL Server Management Import Export Wizard. The files are comma separated.

One row in the file can look like this:

{DCAD82A9-32EC-4351-BEDC-2F8291B40AB3},,{ca91e768-072d-4e30-aaf1-bfe32c24008f},900001:1792756,900001:1792757,basladdning,2011-04-29 02:54:15.380000000,basladdning,2011-04-29 02:54:15.380000000,{20A3C50E-8029-41DE-86F1-DDCDB9A78BA5}

The error I get is:

System.InvalidOperationException was unhandled
Message=The given value of type String from the data source cannot be converted to type uniqueidentifier of the specified target column.

So the problem is the converting from string to GUID.

I have tried the following:

  • Tried different encodings, UTF8 and ANSI
  • Removed the { } surrounding the GUIDS
  • Added ' ' around the GUIDS, with and without { }

Any suggestions? In the flat file where the '' is, is a column that also has Guid as datatype. The column is NULL where the wizard exported it as ''. Can that be the problem?

The code:

using (var file = new StreamReader(filePath))
using (var csv = new CsvReader(file, false)) 
using (var bcp = new SqlBulkCopy(CreateConnectionString()))
{
    bcp.DestinationTableName = "table";
    bcp.WriteToServer(csv);
}

Table definition:

CREATE TABLE [beata].[T_FeatureInstance]( 
    [FeatureInstanceId] [uniqueidentifier] NOT NULL, 
    [FeatureInstanceParentId] [uniqueidentifier] NULL, 
    [FeatureTypeAliasId] [uniqueidentifier] NOT NULL, 
    [Uuid] [varchar](1000) NOT NULL, 
    [VersionId] [varchar](50) NOT NULL, 
    [SkapadAv] [varchar](255) NULL, 
    [Skapad] [datetime] NOT NULL, 
    [UppdateradAv] [varchar](255) NOT NULL, 
    [Uppdaterad] [datetime] NOT NULL, 
    [Gs] [uniqueidentifier] NOT NULL,
Artemix
  • 2,113
  • 2
  • 23
  • 34
Magnus A
  • 83
  • 1
  • 1
  • 5
  • 2
    Check that number of columns and their types are matching your csv. Looks like DB Guid column is being updated with non-guid data from csv. As far as I know Microsoft states that curly braces are valid in GUID strings. – Artemix Nov 29 '12 at 14:14
  • @Artemix is almost certainly correct. It would be worth posting your destination table DDL. – Matt Whitfield Nov 29 '12 at 14:23
  • Ok im not sure how you guys wants this posted but I hope the info needed is here; CREATE TABLE [beata].[T_FeatureInstance]( [FeatureInstanceId] [uniqueidentifier] NOT NULL, [FeatureInstanceParentId] [uniqueidentifier] NULL, [FeatureTypeAliasId] [uniqueidentifier] NOT NULL, [Uuid] [varchar](1000) NOT NULL, [VersionId] [varchar](50) NOT NULL, [SkapadAv] [varchar](255) NULL, [Skapad] [datetime] NOT NULL, [UppdateradAv] [varchar](255) NOT NULL, [Uppdaterad] [datetime] NOT NULL, [Gs] [uniqueidentifier] NOT NULL, – Magnus A Nov 29 '12 at 14:27
  • 1
    Note that in different databases the table may have different column order. – Artemix Nov 29 '12 at 15:10
  • @MagnusA, have you solved this problem. I am facing the same issue. – jaxxbo May 25 '13 at 04:05

4 Answers4

8

In your DataTable.Columns.Add include the data type as a second parameter. That should do the trick. Example:

DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(Guid));
Paul Kar.
  • 1,293
  • 2
  • 21
  • 32
  • +1 I'm not sure why this was downvoted but it was my problem. I was creating the DataTable with column names only, and that worked fine except, strangely, for GUIDs. There is a typecast exception nested in the Exception, so I think this is a bug more than anything, since you can query and insert uniqueidentifiers with string values in SQL Server. – makhdumi Jul 21 '17 at 20:40
  • @Al-Muhandis thanks. Not sure either. A good practice to down voting, or even up voting is giving your reason in the comments section. That way everyone can understand/discuss why there might be a problem with someone's solution. – Paul Kar. Jul 21 '17 at 21:46
  • This is the answer – Assaf S. Feb 01 '23 at 07:13
4

The problem is that the empty column in {DCAD82A9-32EC-4351-BEDC-2F8291B40AB3},,{ca91e768-072d-4e30-aaf1-bfe32c24008f} is being interpreted as '' instead of NULL. I just tried an INSERT STATEMENT on my local database, and that error went away when I specified NULL instead of '' There is another error you might get:

Conversion failed when converting date and/or time from character string.

The reason for this is that SQL only allows 3 digit decimal precision for milliseconds. So you have to round off '2011-04-29 02:54:15.380000000' to '2011-04-29 02:54:15.380', and it will work fine.

One way of doing it would be as mentioned here Alternatively, you could modify CsvReader code to return DBNULL when the value is string.Empty in CsvReader.GetValue(int) function. Have a look here

tranceporter
  • 2,241
  • 1
  • 21
  • 23
  • 1
    I like to add for others that may get the same problem that is was several problems. Beside the thing tranceporter answered above, Artemix was also corrct by telling me that the column order was important. I solved it by building a DataTable with columnnames från the actual table and did the mapping at the DataTable. The Datatable was the input to the SqlBulkCopy class. By setting the allow DbNull for the column it loads correctly. – Magnus A Dec 05 '12 at 11:10
4

I think I found answer for those who are using a popular freeware code class called BulkDataReader. I have the same issue and I tried to post this a bit ago but was since I was asking another question and not providing an answer that post was removed. That's ok I didn't realize that was against the rules.

This time, I've got an answer so hopefully this post will stay on here. I'm going to leave the setup portion of my problem so it's well defined and nobody is confused as to why the solution doesn't work for them. I got the code for the BulkDataReader class from a colleague and it seems he might have gotten that from another well-known answer source so everyone will know how to find BuldDataReader from a search engine.

This issue is setup like this:

I too, tried all sort of formats for the GUIDS in the CSV file including: N’3192E434-F479-4B32-B516-AC658F4B7B89’ {3192E434-F479-4B32-B516-AC658F4B7B89} (3192E434-F479-4B32-B516-AC658F4B7B89) “{3192E434-F479-4B32-B516-AC658F4B7B89}”

A real sample line from my CSV would be: 1,AAPL,452.2012,2013-01-24 13:24:27.000,3192E434-F479-4B32-B516-AC658F4B7B89,3192E434-F479-4B32-B516-AC658F4B7B89

If I remove the 2 Guids and import to a table without those columns it works fine. With the Guids to Unique Identifier columns I get this error: Message = {"The given value of type String from the data source cannot be converted to type unique identifier of the specified target column."}

My control code is pretty basic where the BulkDataReader is quite cumbersome to walk through so be prepared if you're trying to debug it.

using (IDataReader reader = new BulkDataReader(new StreamReader("C:\\test.csv"), false))
        {
            String connectionStr = GetConnString();
            SqlConnection connection = new SqlConnection(connectionStr);
            connection.Open();

            SqlTransaction transaction = connection.BeginTransaction();
            try
            {
                SqlBulkCopy copy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction);
                copy.DestinationTableName = "TestTable6";
                copy.WriteToServer(reader); //ERROR OCCURS HERE: The given value of type String from the data source cannot be converted to type uniqueidentifier of the specified target column
                transaction.Commit();
            }
            catch (Exception exe)
            {
                transaction.Rollback();
            }
            finally
            {
                connection.Close();
            }
        }

So the error is actually occurring within the .NET SqlBulkCopy class. However, it happens right after the BulkDataReader reads a Guid value. That GetValue method is called from External Code meaning it's buried in the plumbing between the BulkDataReader's streamReader and the StreamWriting stuff in the SqlBulkCopy class. It wasn't necessary to dig into all that using my favorite reflection utility. I found that when the BulkDataReader's method IDataRecord.GetValue(int i) returns a string that is really a Guid, SqlBulkCopy cannot convert that string to a Unique Identifier no matter what format it's in. There is likely some obscure and encoding format but I could not find one that would work. However, if I simply return the value as a proper System.Guid type, then SqlBulkCopy converts it to unique identifier just fine. Thus, a simple solution to what seems to be a nightmarish serialization issue. Just copy over the entire IDataRecord.GetValue(int i) method with this and it should work. I tried many of the CLR to SQL data types but not all of them so there could still be another one where you have to play this deserialization game but this should solve the issue for the most part.

  object IDataRecord.GetValue(int i)
    {
        ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed);

        if (((IDataRecord)this).IsDBNull(i))
            return DBNull.Value;
        else
        {
            //For some reason the SqlBulkCopy.WriteToServer method will call this GetValue method when reading 
            //the value and it doesn't seem to know how to convert the string value of a Guid to a unique identifier.
            //However, it does actually know how to convert a System.Guid to a UniqueIdentifer so we can return that if
            //the value parses to Guid
            if (IsGuid(this[i]))
                return Guid.Parse(this[i]);
            else
                return this[i];
        }
    }

    bool IsGuid(object value)
    {
        if (value != null)
        {
            Guid testGuid = Guid.Empty;
            if (Guid.TryParse(value.ToString(), out testGuid))
                return true;
        }

        return false;
    }

I hope this helps someone and sorry I broke the blog rules the first time.

Doug
  • 215
  • 2
  • 6
  • this is a great find. And I was facing the same issue. But please explain how can I have bulk copy use the custom function GetValue()? Really Appreciate your help... – jaxxbo May 25 '13 at 04:04
0

A standard insert into a uniqueidentifier column would look like this (tested and works), so that answers the formating question. Does the column allow nulls?

INSERT INTO dbo.TableName([GUID]) VALUES('20A3C50E-8029-41DE-86F1-DDCDB9A78BA5')

smoore4
  • 4,520
  • 3
  • 36
  • 55
  • Yes it does, and as stated in post I have tried that formating. Maybe formating isnt the problem then. – Magnus A Nov 29 '12 at 14:14