Reputation: 1579
I'm having performance issues inserting a large amount of data into SQL using Entity Framework even with validation off.
I saw online that inserting a DataTable into a stored procedure with a table parameter is extremely quick, but I don't want to be writing this type of code to arrange a DataTable to be inserted:
var table = new DataTable();
table.Columns.Add("Id");
table.Columns.Add("Store");
table.Columns.Add("Name");
table.Columns.Add("Amount");
table.Columns.Add("Active");
table.Columns.Add("Status");
foreach (var s in model.Sales)
{
var row = table.NewRow();
row["Id"] = l.Id;
row["Store"] = (int)l.Store;
row["Name"] = l.Name;
row["Amount"] = l.Amount;
row["Active"] = true;
row["Status"] = string.IsNullOrEmpty(l.Status) ? null : l.Status;
table.Rows.Add(row);
}
var parameter = new SqlParameter("Table", SqlDbType.Structured)
{
Value = table,
TypeName = "SqlDefinedTableType"
};
How can I make a DataTable from a collection and use it as an SQL table parameter?
Upvotes: 0
Views: 2027
Reputation: 1579
You can solve problems like these using generics. Here is a method that converts a collection of a type into a DataTable:
public static DataTable CreateDataTable<T>(ICollection<T> values)
{
var table = new DataTable();
// Get the generic type from the collection
var type = values.GetType().GetGenericArguments()[0];
// Add columns base on the type's properties
foreach (var property in type.GetProperties())
{
/* It is necessary to evaluate whether each property is nullable or not.
* This is because DataTables only support null values in the form of
* DBNull.Value.
*/
var propertyType = property.PropertyType;
var computedType =
// If the type is nullable
propertyType.IsGenericType
&& propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)
// Get its underlying type
? propertyType.GetGenericArguments()[0]
// If it isn't, get return the property type.
: propertyType;
table.Columns.Add(new DataColumn(property.Name, computedType));
}
// Add rows into the DataTable based off of the values
foreach (var value in values)
{
var row = table.NewRow();
foreach (var property in value.GetType().GetProperties())
{
// Create a container to hold the data in the value
object data = null;
// If the property we are adding exists...
if (row.Table.Columns.Contains(property.Name))
// Then get the value of that property
data = value.GetType().GetProperty(property.Name).GetValue(value, null);
// If the value is null, convert the value to DBNull
row[property.Name] = data ?? DBNull.Value;
}
table.Rows.Add(row);
}
return table;
}
You can then use the DataTable returned from the function above to create an SqlParameter
and pass it to the SqlCommand
of your choice.
var parameter = new SqlParameter("Table", SqlDbType.Structured)
{
Value = CreateDataTable(model.Sales),
TypeName = "SqlDefinedTableType"
};
Edit: I edited the function in my original answer to support nullable types.
Upvotes: 2