J'ai un fichier (qui a 10 millions d'enregistrements) comme ci-dessous:
line1
line2
line3
line4
.......
......
10 million lines
Donc, fondamentalement, je veux insérer 10 millions d'enregistrements dans la base de données. j'ai donc lu le fichier et l'ai téléchargé sur SQL Server.
Code C #
System.IO.StreamReader file =
new System.IO.StreamReader(@"c:\test.txt");
while((line = file.ReadLine()) != null)
{
// insertion code goes here
//DAL.ExecuteSql("insert into table1 values("+line+")");
}
file.Close();
mais l'insertion prendra beaucoup de temps. Comment puis-je insérer 10 millions d'enregistrements dans les plus brefs délais en utilisant C #?
Mise à jour 1:
INSERER en vrac:
BULK INSERT DBNAME.dbo.DATAs
FROM 'F:\dt10000000\dt10000000.txt'
WITH
(
ROWTERMINATOR =' \n'
);
Ma table est comme ci-dessous:
DATAs
(
DatasField VARCHAR(MAX)
)
mais j'obtiens l'erreur suivante:
Msg 4866, niveau 16, état 1, ligne 1
Le chargement en bloc a échoué. La colonne est trop longue dans le fichier de données pour la ligne 1, colonne 1. Vérifiez que le terminateur de champ et le terminateur de ligne sont correctement spécifiés.Msg 7399, niveau 16, état 1, ligne 1
Le fournisseur de base de données OLE "BULK" pour le serveur lié "(null)" a signalé une erreur. Le fournisseur n'a donné aucune information sur l'erreur.Msg 7330, niveau 16, état 2, ligne 1
Impossible de récupérer une ligne de OLE fournisseur de base de données "BULK" pour le serveur lié "(null)".
Le code ci-dessous a fonctionné:
BULK INSERT DBNAME.dbo.DATAs
FROM 'F:\dt10000000\dt10000000.txt'
WITH
(
FIELDTERMINATOR = '\t',
ROWTERMINATOR = '\n'
);
Veuillez ne pas créer un DataTable
à charger via BulkCopy. C'est une bonne solution pour les petits ensembles de données, mais il n'y a absolument aucune raison de charger les 10 millions de lignes en mémoire avant d'appeler la base de données.
Votre meilleur pari (en dehors de BCP
/BULK INSERT
/OPENROWSET(BULK...)
) est de diffuser le contenu du fichier dans la base de données via un paramètre table (TVP). En utilisant un TVP, vous pouvez ouvrir le fichier, lire une ligne et envoyer une ligne jusqu'à la fin, puis fermer le fichier. Cette méthode a une empreinte mémoire d'une seule ligne. J'ai écrit un article, Streaming Data Into SQL Server 2008 From an Application , qui contient un exemple de ce scénario.
Un aperçu simpliste de la structure est le suivant. J'assume le même nom de table d'importation et de champ comme indiqué dans la question ci-dessus.
Objets de base de données requis:
-- First: You need a User-Defined Table Type
CREATE TYPE ImportStructure AS TABLE (Field VARCHAR(MAX));
GO
-- Second: Use the UDTT as an input param to an import proc.
-- Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
@ImportTable dbo.ImportStructure READONLY
)
AS
SET NOCOUNT ON;
-- maybe clear out the table first?
TRUNCATE TABLE dbo.DATAs;
INSERT INTO dbo.DATAs (DatasField)
SELECT Field
FROM @ImportTable;
GO
Le code d'application C # pour utiliser les objets SQL ci-dessus est ci-dessous. Remarquez comment plutôt que de remplir un objet (par exemple DataTable) puis d'exécuter la procédure stockée, dans cette méthode, c'est l'exécution de la procédure stockée qui lance la lecture du contenu du fichier. Le paramètre d'entrée du Stored Proc n'est pas une variable; c'est la valeur de retour d'une méthode, GetFileContents
. Cette méthode est appelée lorsque SqlCommand
appelle ExecuteNonQuery
, ce qui ouvre le fichier, lit une ligne et l'envoie à SQL Server via IEnumerable<SqlDataRecord>
Et yield return
construit, puis ferme le fichier. La procédure stockée ne voit qu'une variable de table, @ImportTable, qui peut être accessible dès que les données commencent à arriver ( remarque: les données persistent pendant une courte période, même si elles ne sont pas complètes) contenu, dans tempdb ).
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;
private static IEnumerable<SqlDataRecord> GetFileContents()
{
SqlMetaData[] _TvpSchema = new SqlMetaData[] {
new SqlMetaData("Field", SqlDbType.VarChar, SqlMetaData.Max)
};
SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
StreamReader _FileReader = null;
try
{
_FileReader = new StreamReader("{filePath}");
// read a row, send a row
while (!_FileReader.EndOfStream)
{
// You shouldn't need to call "_DataRecord = new SqlDataRecord" as
// SQL Server already received the row when "yield return" was called.
// Unlike BCP and BULK INSERT, you have the option here to create a string
// call ReadLine() into the string, do manipulation(s) / validation(s) on
// the string, then pass that string into SetString() or discard if invalid.
_DataRecord.SetString(0, _FileReader.ReadLine());
yield return _DataRecord;
}
}
finally
{
_FileReader.Close();
}
}
La méthode GetFileContents
ci-dessus est utilisée comme valeur de paramètre d'entrée pour la procédure stockée, comme indiqué ci-dessous:
public static void test()
{
SqlConnection _Connection = new SqlConnection("{connection string}");
SqlCommand _Command = new SqlCommand("ImportData", _Connection);
_Command.CommandType = CommandType.StoredProcedure;
SqlParameter _TVParam = new SqlParameter();
_TVParam.ParameterName = "@ImportTable";
_TVParam.TypeName = "dbo.ImportStructure";
_TVParam.SqlDbType = SqlDbType.Structured;
_TVParam.Value = GetFileContents(); // return value of the method is streamed data
_Command.Parameters.Add(_TVParam);
try
{
_Connection.Open();
_Command.ExecuteNonQuery();
}
finally
{
_Connection.Close();
}
return;
}
Notes complémentaires:
SELECT
dans le proc.SqlBulkCopy
: SqlBulkCopy
est uniquement INSERT alors que l'utilisation d'un TVP permet d'utiliser les données de n'importe quelle manière: vous pouvez appeler MERGE
; vous pouvez DELETE
en fonction d'une condition; vous pouvez diviser les données en plusieurs tables; etc.ExecuteReader
au lieu de ExecuteNonQuery
. Par exemple, s'il y avait un champ IDENTITY
sur la table d'importation DATAs
, vous pourriez ajouter une clause OUTPUT
à la INSERT
pour renvoyer INSERTED.[ID]
(En supposant que ID
est le nom du champ IDENTITY
). Ou vous pouvez renvoyer les résultats d'une requête complètement différente, ou les deux, car plusieurs ensembles de résultats peuvent être envoyés et accessibles via Reader.NextResult()
. Récupérer des informations de la base de données n'est pas possible lors de l'utilisation de SqlBulkCopy
mais il y a plusieurs questions ici sur S.O. des personnes voulant faire exactement cela (au moins en ce qui concerne les valeurs IDENTITY
nouvellement créées).En C #, la meilleure solution est de laisser le SqlBulkCopy
lire le fichier. Pour ce faire, vous devez passer un IDataReader
directement à SqlBulkCopy.WriteToServer
méthode. Voici un exemple: http://www.codeproject.com/Articles/228332/IDataReader-implementation-plus-SqlBulkCopy
la meilleure façon est de mélanger votre 1ère et la 2ème solution, créez DataTable
et dans la boucle ajoutez-y des lignes puis utilisez BulkCopy
pour télécharger vers la base de données dans une connexion tilisez ceci pour aide en copie groupée
une autre chose pour faire attention que la copie en bloc est une opération très sensible que presque chaque erreur annulera la copie, par exemple si vous déclarez le nom de la colonne dans le dataTable comme "texte" et dans la base de données son "texte", il lèvera une exception , bonne chance.