Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Начните с метода 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. Ниже показан добавленный код:
private readonly PropertyInfo[] _propertyInfos;
private readonly Dictionary<int, string> _nameDictionary;
public MyDataReader(List<T> records)
{
Records = records;
_propertyInfos = typeof(T).GetProperties();
_nameDictionary = new Dictionary<int,string>();
}
Модифицируйте конструктор, чтобы он принимал строку подключения SQLConnection, а также строки для имен схемы и таблицы, куда будут вставлены записи, и добавьте для этих значений переменные уровня класса:
private readonly SqlConnection _connection;
private readonly string _schema;
private readonly string _tableName;
public MyDataReader(List<T> records, SqlConnection connection,
string schema, string tableName)
{
Records = records;
_propertyInfos = typeof(T).GetProperties();
_nameDictionary = new Dictionary<int, string>();
_connection = connection;
_schema = schema;
_tableName = tableName;
}
Далее реализуйте метод GetSchemaTable(), который извлекает информацию SQL Server, касающуюся целевой таблицы:
public DataTable GetSchemaTable()
{
using var schemaCommand =
new SqlCommand($"SELECT * FROM {_schema}.{_tableName}", _connection);
using var reader = schemaCommand.ExecuteReader(CommandBehavior.SchemaOnly);
return reader.GetSchemaTable();
}
Модифицируйте конструктор, чтобы использовать SchemaTable для создания словаря, который содержит поля целевой таблицы в порядке их следования внутри базы данных:
public MyDataReader(List<T> records, SqlConnection connection,
string schema, string tableName)
{
...
DataTable schemaTable = GetSchemaTable();
for (int x = 0; x<schemaTable?.Rows.Count;x++)
{
DataRow col = schemaTable.Rows[x];
var columnName = col.Field<string>("ColumnName");
_nameDictionary.Add(x,columnName);
}
}
Теперь показанные далее методы могут быть реализованы обобщенным образом, используя полученную посредством рефлексии информацию:
public int FieldCount => _propertyInfos.Length;
public object GetValue(int i)
=> _propertyInfos
.First(x=>x.Name.Equals(_nameDictionary[i],
StringComparison.OrdinalIgnoreCase))
.GetValue(Records[_currentIndex]);
Для справки ниже приведены остальные методы, которые должны присутствовать (но не реализованы):
public string GetName(int i) => throw new NotImplementedException();
public int GetOrdinal(string name) => throw new NotImplementedException();
public string GetDataTypeName(int i) => throw new NotImplementedException();
public Type GetFieldType(int i) => throw new NotImplementedException();
public int GetValues(object[] values) => throw new NotImplementedException();
public bool GetBoolean(int i) => throw new NotImplementedException();
public byte GetByte(int i) => throw new NotImplementedException();
public long GetBytes(int i, long fieldOffset, byte[] buffer,
int bufferoffset, int length)
=> throw new NotImplementedException();
public char GetChar(int i) => throw new NotImplementedException();
public long GetChars(int i, long fieldoffset, char[] buffer,
int bufferoffset, int length)
=> throw new NotImplementedException();
public Guid GetGuid(int i) => throw new NotImplementedException();
public short GetInt16(int i) => throw new NotImplementedException();
public int GetInt32(int i) => throw new NotImplementedException();
public long GetInt64(int i) => throw new NotImplementedException();
public float GetFloat(int i) => throw new NotImplementedException();
public double GetDouble(int i) => throw new NotImplementedException();
public string GetString(int i) => throw new NotImplementedException();
public decimal GetDecimal(int i) => throw new NotImplementedException();
public DateTime GetDateTime(int i) => throw new NotImplementedException();
public IDataReader GetData(int i) => throw new NotImplementedException();
public bool IsDBNull(int i) => throw new NotImplementedException();
object IDataRecord.this[int i] => throw new NotImplementedException();
object IDataRecord.this[string name] => throw new NotImplementedException();
public void Close() => throw new NotImplementedException();
public DataTable GetSchemaTable() => throw new NotImplementedException();
public bool NextResult() => throw new NotImplementedException();
public int Depth { get; }
public bool IsClosed { get; }
public int RecordsAffected { get; }
Выполнение массового копирования
Добавьте в папку BulkImport новый файл открытого статического класса по имени ProcessBulkImport.cs. Поместите в начало файла следующие операторы using:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Microsoft.Data.SqlClient;
Добавьте код для поддержки открытия и закрытия подключений (похожий на код в классе InventoryDal):
private const string ConnectionString =
@"Data Source=.,5433;User Id=sa;Password=P@ssw0rd;Initial Catalog=AutoLot";
private static SqlConnection _sqlConnection = null;
private static void OpenConnection()
{
_sqlConnection = new SqlConnection
{
ConnectionString = ConnectionString
};
_sqlConnection.Open();
}
private static