J'ai un DataTable et j'ai besoin que tout soit poussé vers une table de base de données.
Je peux tout mettre là-dedans avec un foreach et insérer chaque ligne à la fois. Cela va très lentement car il y a quelques milliers de lignes.
Existe-t-il un moyen de faire la totalité de la table de données à la fois qui pourrait être plus rapide?
Le DataTable a moins de colonnes que la table SQL. les autres doivent être laissés NULL.
J'ai découvert que SqlBulkCopy est un moyen simple de le faire et ne nécessite pas d'écriture d'une procédure stockée dans SQL Server.
Voici un exemple de la façon dont je l'ai implémenté:
// take note of SqlBulkCopyOptions.KeepIdentity , you may or may not want to use this for your situation.
using (var bulkCopy = new SqlBulkCopy(_connection.ConnectionString, SqlBulkCopyOptions.KeepIdentity))
{
// my DataTable column names match my SQL Column names, so I simply made this loop. However if your column names don't match, just pass in which datatable name matches the SQL column name in Column Mappings
foreach (DataColumn col in table.Columns)
{
bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
}
bulkCopy.BulkCopyTimeout = 600;
bulkCopy.DestinationTableName = destinationTableName;
bulkCopy.WriteToServer(table);
}
Étant donné que vous disposez déjà d'un DataTable et que je suppose que vous utilisez SQL Server 2008 ou mieux, c'est probablement le moyen le plus simple. Tout d'abord, dans votre base de données, créez les deux objets suivants:
CREATE TYPE dbo.MyDataTable -- you can be more speciifc here
AS TABLE
(
col1 INT,
col2 DATETIME
-- etc etc. The columns you have in your data table.
);
GO
CREATE PROCEDURE dbo.InsertMyDataTable
@dt AS dbo.MyDataTable READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.RealTable(column list) SELECT column list FROM @dt;
END
GO
Maintenant dans votre code C #:
DataTable tvp = new DataTable();
// define / populate DataTable
using (connectionObject)
{
SqlCommand cmd = new SqlCommand("dbo.InsertMyDataTable", connectionObject);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("@dt", tvp);
tvparam.SqlDbType = SqlDbType.Structured;
cmd.ExecuteNonQuery();
}
Si vous aviez donné des détails plus précis dans votre question, j'aurais donné une réponse plus précise.
Considérez cette approche , vous n'avez pas besoin d'une boucle for:
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName =
"dbo.BulkCopyDemoMatchingColumns";
try
{
// Write from the source to the destination.
bulkCopy.WriteToServer(ExitingSqlTableName);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Je préfère le type de données défini par l'utilisateur: il est super rapide.
Étape 1: créer une table définie par l'utilisateur dans Sql Server DB
CREATE TYPE [dbo].[udtProduct] AS TABLE(
[ProductID] [int] NULL,
[ProductName] [varchar](50) NULL,
[ProductCode] [varchar](10) NULL
)
GO
Étape 2: créer une procédure stockée avec un type défini par l'utilisateur
CREATE PROCEDURE ProductBulkInsertion
@product udtProduct readonly
AS
BEGIN
INSERT INTO Product
(ProductID,ProductName,ProductCode)
SELECT ProductID,ProductName,ProductCode
FROM @product
END
Étape 3: exécuter la procédure stockée à partir de c #
SqlCommand sqlcmd = new SqlCommand("ProductBulkInsertion", sqlcon);
sqlcmd.CommandType = CommandType.StoredProcedure;
sqlcmd.Parameters.AddWithValue("@product", productTable);
sqlcmd.ExecuteNonQuery();
Problème possible: modifier la table définie par l'utilisateur
En fait, il n'y a pas de commande SQL Server pour modifier le type défini par l'utilisateur. Mais dans Management Studio, vous pouvez y parvenir à partir des étapes suivantes
1. générer un script pour le type (dans une nouvelle fenêtre de requête ou sous forme de fichier) 2. supprimer la table défiée par l'utilisateur. 3. modifiez le script de création, puis exécutez-le.
Si peut s'écarter un peu du chemin droit de DataTable -> table SQL, cela peut aussi se faire via une liste d'objets:
1) DataTable -> Liste générique d'objets
public static DataTable ConvertTo<T>(IList<T> list)
{
DataTable table = CreateTable<T>();
Type entityType = typeof(T);
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType);
foreach (T item in list)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
{
row[prop.Name] = prop.GetValue(item);
}
table.Rows.Add(row);
}
return table;
}
La source et plus de détails peuvent être trouvés ici . Les propriétés manquantes conserveront leurs valeurs par défaut (0 pour int
s, null pour les types de référence, etc.)
2) Poussez les objets dans la base de données
Une façon consiste à utiliser EntityFramework.BulkInsert
extension. Un contexte de données EF est cependant requis.
Il génère la commande BULK INSERT requise pour l'insertion rapide (la solution de type table définie par l'utilisateur est beaucoup plus lente que cela).
Bien que ce ne soit pas la méthode directe, cela aide à construire une base de travail avec une liste d'objets au lieu de DataTable
s qui semble être beaucoup plus efficace en mémoire .
Vous pouvez le faire avec des paramètres de valeur de table.
Jetez un œil à l'article suivant:
http://www.codeproject.com/Articles/39161/C-and-Table-Value-Parameters