Исследование класса SqlBulkCopy
Класс
SqlBulkCopy
имеет один метод,
WriteToServer()
(и его асинхронную версию
WriteToServerAsync()
), который обрабатывает список записей и помещает данные в базу более эффективно, чем последовательность операторов
Insert
, выполненная с помощью объектов команд. Метод
WriteToServer()
перегружен, чтобы принимать объект
DataTable
, объект
DataReader
или массив объектов
DataRow
. Придерживаясь тематики главы, мы собираемся использовать версию
WriteToServer()
, которая принимает
DataReader
, так что необходимо создать специальный класс чтения данных.
Создание специального класса чтения данных
Желательно, чтобы специальный класс чтения данных был обобщенным и содержал список моделей, которые нужно импортировать. Создайте в проекте
AutoLot.DAL
новую папку по имени
BulkImport
, a в ней — новый файл интерфейса
IMyDataReader.cs
, реализующего
IDataReader
, со следующим кодом:
using System.Collections.Generic;
using System.Data;
namespace AutoLot.Dal.BulkImport
{
public interface IMyDataReader<T> : IDataReader
{
List<T> Records { get; set; }
}
}
Далее реализуйте специальный класс чтения данных. Как вы уже видели, классы чтения данных содержат много частей, отвечающих за перемещение данных. Хорошая новость в том, что для
SqlBulkCopy
придется реализовать лишь несколько из них. Создайте новый файл класса по имени
MyDataReader.cs
и добавьте в него перечисленные ниже операторы
using
:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
Сделайте класс открытым и запечатанным и обеспечьте реализацию классом интерфейса
IMyDataReader
. Добавьте конструктор для принятия записей и установки свойства:
public sealed class MyDataReader<T> : IMyDataReader<T>
{
public List<T> Records { get; set; }
public MyDataReader(List<T> records)
{
Records = records;
}
}
Предложите Visual Studio или Visual Studio Code самостоятельно реализовать все методы (либо скопировать их), что даст вам отправную точку для специального класса чтения данных. В рассматриваемом сценарии потребуется реализовать лишь члены, кратко описанные в табл. 21.7.
Начните с метода
Read()
, который возвращает
false
, если класс для чтения находится в конце списка, или
true
(с инкрементированием счетчика уровня класса), если конец списка еще не достигнут. Добавьте переменную уровня класса, которая будет хранить текущий индекс
List<T>
, и обновите метод
Read()
, как показано ниже:
public class MyDataReader<T> : IMyDataReader<T>
{
...
private int _currentIndex = -1;
public bool Read()
{
if (_currentIndex + 1 >= Records.Count)
{
return false;
}
_currentIndex++;
return true;
}
}
Каждый метод
GetXXX()
и свойство
FieldCount
требуют знания специфической модели, подлежащей загрузке. Вот как выглядит метод
GetValue()
, использующий
CarViewModel
:
public object GetValue(int i)
{
Car currentRecord = Records[_currentIndex] as Car;
return i switch
{
0 => currentRecord.Id,
1 => currentRecord.MakeId,
2 => currentRecord.Color,
3 => currentRecord.PetName,
4 => currentRecord.TimeStamp,
_ => string.Empty,
};
}
База данных содержит только четыре таблицы, но это означает необходимость в наличии четырех вариаций класса чтения данных. А подумайте о реальной производственной базе данных, в которой таблиц гораздо больше!Решить проблему можно более эффективно с применением рефлексии (см. главу 17) и LINQ to Objects (см. главу 13).
Добавьте переменные
readonly
для хранения значений
PropertyInfo
модели и словарь, который будет использоваться для хранения местоположения поля и имени таблицы в SQL Server. Модифицируйте конструктор, чтобы он принимал свойства обобщенного типа и инициализировал объект
Dictionary
. Ниже показан добавленный код: