Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Добавление реализации IDisposable
Добавьте к определению класса интерфейс IDisposable:
public class InventoryDal : IDisposable
{
...
}
Затем реализуйте шаблон освобождения, вызывая Dispose() на объекте SqlConnection:
bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_sqlConnection.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
Добавление методов выборки
Для начала объедините имеющиеся сведения об объектах команд, чтения данных и обобщенных коллекциях, чтобы получить записи из таблицы Inventory. Как было показано в начале главы, объект чтения данных в поставщике делает возможной выборку записей с использованием механизма, который реализует только чтение в прямом направлении с помощью метода Read(). В этом примере свойство CommandBehavior класса DataReader настроено на автоматическое закрытие подключения, когда закрывается объект чтения данных. Метод GetAllInventory() возвращает экземпляр List<CarViewModel>, представляющий все данные в таблице Inventory:
public List<CarViewModel> GetAllInventory()
{
OpenConnection();
// Здесь будут храниться записи.
List<CarViewModel> inventory = new List<CarViewModel>();
// Подготовить объект команды.
string sql =
@"SELECT i.Id, i.Color, i.PetName,m.Name as Make
FROM Inventory i
INNER JOIN Makes m on m.Id = i.MakeId";
using SqlCommand command =
new SqlCommand(sql, _sqlConnection)
{
CommandType = CommandType.Text
};
command.CommandType = CommandType.Text;
SqlDataReader dataReader =
command.ExecuteReader(CommandBehavior.CloseConnection);
while (dataReader.Read())
{
inventory.Add(new CarViewModel
{
Id = (int)dataReader["Id"],
Color = (string)dataReader["Color"],
Make = (string)dataReader["Make"],
PetName = (string)dataReader["PetName"]
});
}
dataReader.Close();
return inventory;
}
Следующий метод выборки получает одиночный объект CarViewModel на основе значения CarId:
public CarViewModel GetCar(int id)
{
OpenConnection();
CarViewModel car = null;
// Параметры должны применяться по причинам, связанным с безопасностью.
string sql =
$@"SELECT i.Id, i.Color, i.PetName,m.Name as Make
FROM Inventory i
INNER JOIN Makes m on m.Id = i.MakeId
WHERE i.Id = {id}";
using SqlCommand command =
new SqlCommand(sql, _sqlConnection)
{
CommandType = CommandType.Text
};
SqlDataReader dataReader =
command.ExecuteReader(CommandBehavior.CloseConnection);
while (dataReader.Read())
{
car = new CarViewModel
{
Id = (int) dataReader["Id"],
Color = (string) dataReader["Color"],
Make = (string) dataReader["Make"],
PetName = (string) dataReader["PetName"]
};
}
dataReader.Close();
return car;
}
На заметку! Помещение пользовательского ввода внутрь низкоуровневых операторов SQL, как делалось здесь, обычно считается неудачной практикой. Позже в главе код будет модифицирован для использования параметров.
Вставка новой записи об автомобиле
Вставка новой записи в таблицу Inventory сводится к построению SQL-оператора Insert (на основе пользовательского ввода), открытию подключения, вызову метода ExecuteNonQuery() с применением объекта команды и закрытию подключения. Увидеть вставку в действии можно, добавив к типу InventoryDal открытый метод по имени InsertAuto(), который принимает три параметра, отображаемые на не связанные с идентичностью столбцы таблицы Inventory (Color, Make и PetName). Указанные аргументы используются при форматировании строки для вставки новой записи. И, наконец, для выполнения итогового оператора SQL применяется объект SqlConnection.
public void InsertAuto(string color, int makeId, string petName)
{
OpenConnection();
// Сформатировать и выполнить оператор SQL.
string sql = $"Insert Into Inventory (MakeId, Color, PetName) Values ('{makeId}',
'{color}', '{petName}')";
// Выполнить, используя наше подключение.
using (SqlCommand command = new SqlCommand(sql, _sqlConnection))
{
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
}
CloseConnection();
}
Приведенный выше метод принимает три значения для Car и работает при условии, что вызывающий код передает значения в правильном порядке. Более совершенный метод использует Car, чтобы стать строго типизированным, гарантируя тем самым, что все свойства передаются методу в корректном порядке.
Создание строго типизированного метода InsertCar()
Добавьте в класс InventoryDal еще одну версию метода InsertAuto(), которая принимает в качестве параметра Car:
public void InsertAuto(Car car)
{
OpenConnection();
// Сформатировать и выполнить оператор SQL.
string sql = "Insert Into Inventory (MakeId, Color, PetName) Values " +
$"('{car.MakeId}', '{car.Color}', '{car.PetName}')";
// Выполнить, используя наше подключение.
using (SqlCommand command = new SqlCommand(sql, _sqlConnection))
{
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
}
CloseConnection();
}
Добавление логики удаления
Удаление существующей записи не сложнее вставки новой записи. В отличие от метода InsertAuto() на этот раз вы узнаете о важном блоке try/catch, который обрабатывает возможную попытку удалить запись об автомобиле, уже заказанном кем-то из таблицы Customers. Стандартные параметры INSERT и UPDATE для внешних ключей по умолчанию предотвращают удаление зависимых записей в связанных таблицах. Когда предпринимается попытка подобного удаления, генерируется исключение SqlException.
В реальной программе была бы предусмотрена логика обработки такой ошибки, но в рассматриваемом примере просто генерируется новое исключение. Добавьте в класс InventoryDal следующий метод:
public void DeleteCar(int id)
{
OpenConnection();
// Получить идентификатор автомобиля, подлежащего удалению,
// и удалить запись о нем.
string sql = $"Delete from