Массовое обновление в С#
Для ввода огромного количества данных в базу данных я собирал всю информацию о вставке в список и преобразовывал этот список в DataTable
. Затем я вставляю этот список в базу данных через SqlBulkCopy
.
Когда я отправляю свой сгенерированный список LiMyList
, который содержит информацию о всех объемных данных, которые я хочу вставить в базу данных, и передаю ее в мою операцию объемной вставки
InsertData(LiMyList, "MyTable");
Где InsertData
есть
public static void InsertData<T>(List<T> list,string TableName)
{
DataTable dt = new DataTable("MyTable");
clsBulkOperation blk = new clsBulkOperation();
dt = ConvertToDataTable(list);
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
using (SqlBulkCopy bulkcopy = new SqlBulkCopy(ConfigurationManager.ConnectionStrings["SchoolSoulDataEntitiesForReport"].ConnectionString))
{
bulkcopy.BulkCopyTimeout = 660;
bulkcopy.DestinationTableName = TableName;
bulkcopy.WriteToServer(dt);
}
}
public static DataTable ConvertToDataTable<T>(IList<T> data)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
Теперь я хочу выполнить операцию обновления, есть ли способ вставить данные, сделанные с помощью SqlBulkCopy
для обновления данных в DataBase From С#.Net
Ответы
Ответ 1
То, что я делал раньше, это выполнить массовую вставку из данных в временную таблицу, а затем использовать команду или хранимую процедуру для обновления данных, относящихся к таблице temp, с помощью таблицы назначения. Темп-таблица является дополнительным шагом, но вы можете получить прирост производительности с массовой вставкой и массовым обновлением, если количество строк велико, по сравнению с обновлением данных по строкам.
Пример:
public static void UpdateData<T>(List<T> list,string TableName)
{
DataTable dt = new DataTable("MyTable");
dt = ConvertToDataTable(list);
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SchoolSoulDataEntitiesForReport"].ConnectionString))
{
using (SqlCommand command = new SqlCommand("", conn))
{
try
{
conn.Open();
//Creating temp table on database
command.CommandText = "CREATE TABLE #TmpTable(...)";
command.ExecuteNonQuery();
//Bulk insert into temp table
using (SqlBulkCopy bulkcopy = new SqlBulkCopy(conn))
{
bulkcopy.BulkCopyTimeout = 660;
bulkcopy.DestinationTableName = "#TmpTable";
bulkcopy.WriteToServer(dt);
bulkcopy.Close();
}
// Updating destination table, and dropping temp table
command.CommandTimeout = 300;
command.CommandText = "UPDATE T SET ... FROM " + TableName + " T INNER JOIN #TmpTable Temp ON ...; DROP TABLE #TmpTable;";
command.ExecuteNonQuery();
}
catch (Exception ex)
{
// Handle exception properly
}
finally
{
conn.Close();
}
}
}
}
Обратите внимание, что для выполнения всей операции используется одно соединение, чтобы иметь возможность использовать временную таблицу на каждом шаге, поскольку объем таблицы temp для каждого соединения.
Ответ 2
В моем личном опыте лучший способ справиться с этой ситуацией - использовать хранимую процедуру с Table-Valued Parameter
и a User-Defined Table Type
. Просто настройте тип с столбцами таблицы данных и передайте в таблицу данных в качестве параметра в команде SQL.
Внутри хранимой процедуры вы можете либо присоединиться к определенному уникальному ключу (если все строки, которые вы обновляете, существуют), либо - если вы можете столкнуться с ситуацией, когда вам нужно делать как обновления, так и вставки - используйте SQL Merge
в хранимой процедуре для обработки как обновлений, так и вставок, как применимо.
В Microsoft есть ссылка на синтаксис и статья с примерами для Merge.
Для части .NET просто установить тип параметра как SqlDbType.Structured
и установить значение указанного параметра в таблицу данных, содержащую записи, которые вы хотите обновить.
Этот метод обеспечивает как ясность, так и простоту обслуживания. Хотя могут быть и способы повышения производительности (например, удаление его во временную таблицу, а затем повторение этой таблицы), я думаю, что они перевешиваются простотой позволить .NET и SQL обрабатывать таблицу и обновлять сами записи. K.I.S.S.
Ответ 3
Попробуйте SqlBulkTools, доступные на Nuget.
Отказ от ответственности: я автор этой библиотеки.
var bulk = new BulkOperations();
var records = GetRecordsToUpdate();
using (TransactionScope trans = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager
.ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
{
bulk.Setup<MyTable>()
.ForCollection(records)
.WithTable("MyTable")
.AddColumn(x => x.SomeColumn1)
.AddColumn(x => x.SomeColumn2)
.BulkUpdate()
.MatchTargetOn(x => x.Identifier)
.Commit(conn);
}
trans.Complete();
}
Только "SomeColumn1" и "SomeColumn2" будут обновлены. Больше примеров можно найти здесь
Ответ 4
Не уверен, что я понял, что вы собираетесь архивировать... Если ваш вопрос касается быстрой замены всего содержимого таблицы, то я бы пошел на truncate
(http://technet.microsoft.com/en-us/library/ms177570. aspx) и массовая вставка новой порции данных. Но этот подход будет работать только в том случае, если у вас нет ограничений внешнего ключа.
Если вы хотите реального обновления, чем искать ответ от Гильермо Гутьеррес.
Ответ 5
Я бы вставлял новые значения во временную таблицу, а затем делал слияние с таблицей назначения, примерно так:
MERGE [DestTable] AS D
USING #SourceTable S
ON D.ID = S.ID
WHEN MATCHED THEN
UPDATE SET ...
WHEN NOT MATCHED
THEN INSERT (...)
VALUES (...);
Ответ 6
Вы можете попытаться создать запрос, содержащий все данные. Используйте case
. Это может выглядеть так.
update your_table
set some_column = case when id = 1 then 'value of 1'
when id = 5 then 'value of 5'
when id = 7 then 'value of 7'
when id = 9 then 'value of 9'
end
where id in (1,5,7,9)
Ответ 7
Я бы выбрал метод TempTable, потому что таким образом вы ничего не блокируете. Но если ваша логика должна быть только в интерфейсе, и вам нужно использовать массовую копию, я бы попытался применить метод "Удалить/Вставить", но в том же SqlTransaction, чтобы обеспечить целостность, которая была бы примерно такой:
// ...
dt = ConvertToDataTable(list);
using (SqlConnection cnx = new SqlConnection(myConnectionString))
{
using (SqlTranscation tran = cnx.BeginTransaction())
{
DeleteData(cnx, tran, list);
using (SqlBulkCopy bulkcopy = new SqlBulkCopy(cnx, SqlBulkCopyOptions.Default, tran))
{
bulkcopy.BulkCopyTimeout = 660;
bulkcopy.DestinationTableName = TabelName;
bulkcopy.WriteToServer(dt);
}
tran.Commit();
}
}
Ответ 8
Массовая операция была бы хорошим способом привести к этому.
Есть репозиторий github, который содержит оба полезных метода: BulkInsert и BulkUpdate с использованием MySql и EF6+.
BulkUpdate/BulkInsert в основном считывает все свойства из вашей общей сущности, а затем создает массовый запрос для вас.
Ps: Это было разработано для моих нужд, и проект открыт для тех, кто хочет улучшить его или изменить его на лучшее решение, которое будет полезно сообществу.
PS2: Если это не решает проблему, попробуйте внести изменения в проект, чтобы улучшить и достичь того, чего вы хотите, по крайней мере, это хорошее начало.
Пожалуйста, посмотрите здесь