29

I am new to JSON and SQLBulkCopy. I have a JSON formatted POST data that I want to Bulk Copy/Insert in Microsoft SQL using C#.

JSON Format:

{
    "URLs": [{
        "url_name": "Google",
        "url_address": "http://www.google.com/"
    },
    {
        "url_name": "Yahoo",
        "url_address": "http://www.yahoo.com/"
    },
    {
        "url_name": "FB",
        "url_address": "http://www.fb.com/"
    },
    {
        "url_name": "MegaSearches",
        "url_address": "http://www.megasearches.com/"
    }]
}

Classes:

public class UrlData
{
    public List<Url> URLs {get;set;}
}

public class Url
{
    public string url_address {get;set;}
    public string url_name {get;set;}
}

How can I do that efficiently?

Leigh
  • 28,765
  • 10
  • 55
  • 103
Ali007
  • 648
  • 1
  • 12
  • 20
  • 3
    bulk copy is for a large number of rows, so you can bypass meticulous logging for deletes and inserts. You don't need this for a single row, which seems to be what you want. How many URLs are you passing in? – ps2goat Sep 17 '13 at 03:51

5 Answers5

46

TL;DR If you have your data already represented as DataTable, you can insert it to the destination table on the server with SqlBulkCopy:

string csDestination = "put here the a connection string to the database";

using (SqlConnection connection = new SqlConnection(csDestination))
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
    connection.Open()
    bulkCopy.DestinationTableName = "TUrls";
    bulkCopy.WriteToServer(dataTableOfUrls);
}

If you want to load just "from 10 to 50 urls" there's no need to use SqlBulkCopy - its general purpose to eliminate thousands of separate inserts.

So, inserting without SqlBulkCopy [and without EntityFramework] can be done one by one:

string insertQuery = "insert into TUrls(address, name) values(@address, @name)";
foreach (URL url in listOfUrls)
{
    SqlCommand cmd = new SqlCommand(insertQuery);
    cmd.Parameters.AddWithValue("@name", url.url_name);
    cmd.Parameters.AddWithValue("@address", url.urld_address);

    // Remember to take care of connection! I omit this part for clarity
    cmd.ExecuteNonQuery();
}

To insert data with SqlBulkCopy you need to convert your data (e.g. a list of custom class objects) to DataTable. Below is the quote from Marc Gravell's answer as an example of generic solution for such conversion:

Here's a nice 2013 update using FastMember from NuGet:

IEnumerable<SomeType> data = ...
DataTable table = new DataTable();
using(var reader = ObjectReader.Create(data)) {
    table.Load(reader);
}

Yes, this is pretty much the exact opposite of this one; reflection would suffice - or if you need quicker, HyperDescriptor in 2.0, or maybe Expression in 3.5. Actually, HyperDescriptor should be more than adequate.

For example:

// remove "this" if not on C# 3.0 / .NET 3.5
public static DataTable ToDataTable<T>(this IList<T> data)
{
    PropertyDescriptorCollection props =
        TypeDescriptor.GetProperties(typeof(T));
    DataTable table = new DataTable();
    for(int i = 0 ; i < props.Count ; i++)
    {
        PropertyDescriptor prop = props[i];
        table.Columns.Add(prop.Name, prop.PropertyType);
    }
    object[] values = new object[props.Count];
    foreach (T item in data)
    {
        for (int i = 0; i < values.Length; i++)
        {
            values[i] = props[i].GetValue(item);
        }
        table.Rows.Add(values);
    }
    return table;        
}

Now, having your data represented as DataTable, you're ready to write it to the destination table on the server:

string csDestination = "put here the a connection string to the database";

using (SqlConnection connection = new SqlConnection(csDestination))
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
    connection.Open();
    bulkCopy.DestinationTableName = "TUrls";
    bulkCopy.WriteToServer(dataTableOfUrls);
}

Hope it helps.

**UPDATE: **

  1. Answer to @pseudonym27 question: "Hello can I use BulkCopy class to append data to existing table in SQL database?"

Yes, you can - BulkCopy works just as an insert command in a way that it appends data.
Also, consider using an intermediate table in case there's high probability for operation to go wrong (e.g. long insert time and connection issues), and you want to busy/lock the destination table as little time as possible. Another use case for an intermediate table is, of course, the need to do some data transformations before an insert.

Harry Adams
  • 423
  • 5
  • 16
pkuderov
  • 3,501
  • 2
  • 28
  • 46
  • Thanks for your help. Is there a way you can deserialize the JSON into DataTable directly, rather than converting the JSON first to some object [URL] and then to DataTable. For example, Object URL can be DataRow and URLData [ListOfURLs] can be DataTable directly from the deserialized JSON. – Ali007 Sep 23 '13 at 01:10
  • @Ali007 I've never been worked with JSON in c# so I exactly don't know, but simple googling tells me that there's no built-in [in .Net] way. So the most popular way - to use JSON.Net (http://james.newtonking.com/projects/json-net.aspx) to deserialize it to IEnumerable and then use Marc Gravell's extension method to convert it to DataTable – pkuderov Sep 23 '13 at 21:18
  • @pseudonym27 sorry, just mentioned your comment. Look at `upd` part at the bottom of my answer – pkuderov Oct 22 '14 at 10:15
  • @pseudonym27 yes, but instead of `Type.GetType("System.Int32")` you can just use `typeof(int)`, `typeof(DateTime)`, `typeof(Int32)` etc. – pkuderov Oct 22 '14 at 12:55
  • @pseudonym27 because generally it's not your business :) you deal with DB driver in .Net world with .Net types. So it's driver's problem to map .Net types onto SQL Server types. In your .Net world you pretend that you know nothing about SQL Server world (especially its inner structure and data types). – pkuderov Oct 22 '14 at 13:45
  • I get a `Procedure or function XXX_sp has too many arguments specified.` one the second round of the foreach loop. First time thought it inserts data to DB, second time it throws error. – MaylorTaylor Dec 18 '14 at 16:51
  • 1
    Gotta open the connection, so this is missing something: `using (SqlConnection destinationConnection = new SqlConnection(csDestination)) using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection)) { bulkCopy.DestinationTableName = "TUrls"; bulkCopy.WriteToServer(dataTableOfUrls); }` ; otherwise gets the error `WriteToServer requires an open and available Connection. The connection's current state is closed.]` – Nate Anderson Apr 15 '21 at 22:37
7

Using this below code, you can convert List<YourClassname> to DataTable:-

List<YourClass> objlist = alldata;
string json = Newtonsoft.Json.JsonConvert.SerializeObject(objlist);
DataTable dt = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(json);
SaveDataInTables(dt, "Table_Name_Of_SQL");

Here, I'm assuming that alldata contains list<YourClass> object and you can also do - objlist.Add(objYourClass), then pass sql_TableName and data table in SaveDataInTables method. This method will insert all data in SQL_Table.

public void SaveDataInTables(DataTable dataTable, string tablename)
{
   if (dataTable.Rows.Count > 0)
   {
       using (SqlConnection con = new SqlConnection("Your_ConnectionString"))
       {
           using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(con))
           {
               sqlBulkCopy.DestinationTableName = tablename;
               con.Open();
               sqlBulkCopy.WriteToServer(dataTable);
               con.Close();
            }
        }
    }
}

Hope, these codes help you!!!

Nitika Chopra
  • 1,281
  • 17
  • 22
2

You should use Table valued parameters. if you are using > sql server 2005. You can have an example here

Ehsan
  • 31,833
  • 6
  • 56
  • 65
2

If it's only 10-50 urls, being inserted infrequently, you can fire off insert statements. Simple and less hassle and you can use something easy and quick like dapper.

Else if you want the Bulk Copy, you would need to create and fill up an ADO.NET datatable from your JSON first - preferably matching the schema of your destination sql table. It's your choice.

Vivek
  • 2,103
  • 17
  • 26
0
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
   bulkCopy.DestinationTableName = "dbo.LogData";
   try
   {
      // Write from the source to the destination.
      connection.Open();
      bulkCopy.WriteToServer(dataTable1);
      connection.Close();
   }
   catch (Exception ex)
   {
      Console.WriteLine(ex.Message);
   }
}
Mohamad Mousheimish
  • 1,641
  • 3
  • 16
  • 48
safeer tp
  • 1
  • 1