Моля, не създайте DataTable
за зареждане чрез BulkCopy. Това е добро решение за по-малки набори от данни, но няма абсолютно никаква причина да зареждате всичките 10 милиона реда в паметта, преди да извикате базата данни.
Най-добрият ви залог (извън BCP
/ BULK INSERT
/ OPENROWSET(BULK...)
) е да предава поточно съдържанието от файла в базата данни чрез параметър с таблица с стойност (TVP). С помощта на TVP можете да отворите файла, да прочетете ред и да изпратите ред, докато приключите, и след това да затворите файла. Този метод има отпечатък на паметта само от един ред. Написах статия, Поточно предаване на данни в SQL Server 2008 от приложение, в която има пример за точно този сценарий.
Опростеният преглед на структурата е както следва. Предполагам същата таблица за импортиране и име на поле, както е показано във въпроса по-горе.
Необходими обекти на базата данни:
-- 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
Кодът на приложението C# за използване на горните SQL обекти е по-долу. Забележете как вместо да попълвате обект (напр. DataTable) и след това да изпълнявате съхранената процедура, в този метод изпълнението на съхранената процедура инициира четенето на съдържанието на файла. Входният параметър на Stored Proc не е променлива; това е върнатата стойност на метод, GetFileContents
. Този метод се извиква, когато SqlCommand
извиква ExecuteNonQuery
, който отваря файла, чете ред и изпраща реда до SQL Server чрез IEnumerable<SqlDataRecord>
и yield return
изгражда и след това затваря файла. Съхранената процедура просто вижда променлива на таблица, @ImportTable, която може да бъде достъпна веднага щом данните започнат да идват (забележка:данните се запазват за кратко време, дори ако не е пълното съдържание, в 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();
}
}
GetFileContents
методът по-горе се използва като стойност на входния параметър за съхранената процедура, както е показано по-долу:
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;
}
Допълнителни бележки:
- С известна модификация горният C# код може да бъде адаптиран, за да пакетира данните.
- С незначителна модификация горният C# код може да бъде адаптиран за изпращане в множество полета (примерът, показан в статията „Данни на пара...“, свързана по-горе, преминава в 2 полета).
- Можете също да манипулирате стойността на всеки запис в
SELECT
изявление в процедурата. - Можете също да филтрирате редове, като използвате условие WHERE в процедурата.
- Можете да получите достъп до променливата на таблицата TVP няколко пъти; той е САМО ЧЕТЕНЕ, но не и "само препращане".
- Предимства пред
SqlBulkCopy
:SqlBulkCopy
е само INSERT, докато използването на TVP позволява данните да се използват по всякакъв начин:можете да извикатеMERGE
; можете даDELETE
въз основа на някакво условие; можете да разделите данните на множество таблици; и така нататък.- Поради това, че TVP не е само INSERT, нямате нужда от отделна таблица за етапи, в която да изхвърляте данните.
- Можете да получите обратно данни от базата данни, като извикате
ExecuteReader
вместоExecuteNonQuery
. Например, ако имашеIDENTITY
полето вDATAs
таблица за импортиране, можете да добавитеOUTPUT
клауза къмINSERT
за да предаде обратноINSERTED.[ID]
(приемайкиID
е името наIDENTITY
поле). Или можете да изпратите обратно резултатите от напълно различна заявка или и двете, тъй като множество набори от резултати могат да бъдат изпратени и достъпни чрезReader.NextResult()
. Получаването на информация от базата данни не е възможно при използване наSqlBulkCopy
все пак има няколко въпроса тук относно S.O. от хората, които искат да направят точно това (поне по отношение на новосъздаденияIDENTITY
стойности). - За повече информация защо понякога е по-бърз за цялостния процес, дори и малко по-бавен при получаването на данните от диска в SQL Server, моля, вижте тази бяла книга от екипа за консултации на клиенти на SQL Server:Максимизиране на производителността с TVP