Здравствуйте, уважаемые хабравчане!
Хочу рассказать о собственной велонадстройке над ADO.NET.
Так как в большинстве своих проектов работу с данными я реализую в хранимых процедурах, идея создания надстройки появилась из-за необходимости чтения «сложных» результатов, например выборки из мастер-таблицы и нескольких подчиненных, а затем заполнения моделей этими данными.
Если кому-нибудь интересно, прошу под кат
Маппинг осуществляется за счет генерации IL кода для инициализации нужного типа из DataRecord и сохранения его в статическом словаре, что устраняет необходимость в повторной генерации кода инициализации для данного типа вне зависимости от инстанции самого DataManager'а, но с учетом вызываемой хранимой процедуры.
Примеры использования:
Создадим класс наследник, реализующий создание подключения к БД:
Выборка простого набора данных:
где хранимка Test выбирает данные, например, такого вида:
Чтение данных из
выбирающей одну главную запись и несколько подчиненных:
где структура классов моделей
Обратите внимание — кроме основных свойств класса инициализируются свойства и вложенных классов-свойств. Для этого необходимо в выборке присваивать именам столбцов правильное наименование, отражающее вложенность — например для UserLocationName позволяет мапперу найти в объекте, для которого маппинг производится (типа ProductComment в данном случае), свойство User, в нем Location и уже нужное нам Name.
Дальше — больше. Получение нескольких главных записей с подчиненными:
Всего я объявил по четыре перегруженных метода для получения записей один ко многим и многие ко многим, позволяющих чтение до четырех наборов подчиненных записей. В случае если подчиненных наборов больше (что довольно редко встречается), можно добавить еще перегрузок, или воспользоваться другим методом:
Ну а если модель данных не подходит под вышеуказанные шаблоны, можно воспользоваться методом Raw — он принимает лямбду, в которой доступен IDataReader, которым можно пользоваться по ситуации.
Конечно же, присутствуют методы получения скалярного значения и старый-добрый Execute.
Добавление параметров для хранимки осуществляется вызовом AddParams
Так же реализован метод для передачи табличных параметров (Table-Valued Parameters) — конечно же он работает только для MS SQL Server начиная с 2008 версии.
По производительности совсем немного отстаем от Dapper, особенно при певром вызове инициализатора объекта.
В планах — реализовать поддержку IQueryable-результатов с передачей параметров в хранимку, что очень пригодилось бы в ApiController'aх AspNet MVC.
Если кому то будет интересен данный велосипед, код библиотеки доступен на github.
Спасибо за внимание!
Хочу рассказать о собственной велонадстройке над ADO.NET.
Так как в большинстве своих проектов работу с данными я реализую в хранимых процедурах, идея создания надстройки появилась из-за необходимости чтения «сложных» результатов, например выборки из мастер-таблицы и нескольких подчиненных, а затем заполнения моделей этими данными.
Если кому-нибудь интересно, прошу под кат
Маппинг осуществляется за счет генерации IL кода для инициализации нужного типа из DataRecord и сохранения его в статическом словаре, что устраняет необходимость в повторной генерации кода инициализации для данного типа вне зависимости от инстанции самого DataManager'а, но с учетом вызываемой хранимой процедуры.
Примеры использования:
Создадим класс наследник, реализующий создание подключения к БД:
class MSSqlDataManager : DataManager
{
public MSSqlDataManager() : base(new SqlConnection("ConnectionString here")) { }
}
Выборка простого набора данных:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? Price { get; set; }
}
...
using (var dm = new MSSqlDataManager())
{
List<Product> res = dm.Procedure("Test").GetList<Product>();
}
где хранимка Test выбирает данные, например, такого вида:
SELECT p.Id, p.Name, p.[Description], p.Price
FROM dbo.Product p
Чтение данных из
хранимки,
SELECT
p.Id
, p.Name,
, p.[Description]
, p.Price
, StorageId = s.Id
, StorageName = s.Name
FROM dbo.Product p
INNER JOIN dbo.Storages s ON s.Id = p.StorageId
WHERE p.Id = @Id;
SELECT
c.Id
, c.Body
, c.WriteDate
, UserId = u.Id
, UserName = u.Name
, UserLocationId = l.Id
, UserLocationName = l.Name
, c.ProductId
FROM dbo.Comments c
INNER JOIN dbo.Users u ON u.Id = c.UserId
INNER JOIN dbo.Locations l ON l.Id = u.LocationId
WHERE c.ProductId = @Id;
выбирающей одну главную запись и несколько подчиненных:
Product res = dm.Procedure("Test").AddParams(new { id = 10 }).Get<Product, ProductComment>(p => p.Comments);
где структура классов моделей
такова
public class UserLocation
{
public int Id { get; set; }
public string Name { get; set; }
}
public class UserModel
{
public int Id { get; set; }
public string Name { get; set; }
public UserLocation Location { get; set; }
public UserModel()
{
this.Location = new UserLocation();
}
}
public class ProductComment
{
public int Id { get; set; }
public string Body { get; set; }
public DateTime WriteDate { get; set; }
public UserModel User { get; set; }
public int ProductId { get; set; }
public ProductComment()
{
this.User = new UserModel();
}
}
public class ProductStorage
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? Price { get; set; }
public ProductStorage Storage { get; set; }
public List<ProductComment> Comments { get; set; }
public Product()
{
this.Storage = new ProductStorage();
this.Comments = new List<ProductComment>();
}
}
Обратите внимание — кроме основных свойств класса инициализируются свойства и вложенных классов-свойств. Для этого необходимо в выборке присваивать именам столбцов правильное наименование, отражающее вложенность — например для UserLocationName позволяет мапперу найти в объекте, для которого маппинг производится (типа ProductComment в данном случае), свойство User, в нем Location и уже нужное нам Name.
Дальше — больше. Получение нескольких главных записей с подчиненными:
List<Product> res = dm.Procedure("Test")
.GetList<Product, ProductComment>(
(parents, detail)=>parents.First(p => p.Id == detail.ProductId).Comments
);
Всего я объявил по четыре перегруженных метода для получения записей один ко многим и многие ко многим, позволяющих чтение до четырех наборов подчиненных записей. В случае если подчиненных наборов больше (что довольно редко встречается), можно добавить еще перегрузок, или воспользоваться другим методом:
List<Product> res = dm.Procedure("Test")
.GetList<Product>(
(dr, parents) => {
parents.Where(p=>p.Id == (int)dr["ProductId"]).First().Comments
.Add(dm.Create<ProductComment>(dr));
},
(dr, parents) => { },
...
);
Ну а если модель данных не подходит под вышеуказанные шаблоны, можно воспользоваться методом Raw — он принимает лямбду, в которой доступен IDataReader, которым можно пользоваться по ситуации.
dm.Procedure("Test")
.Raw(dr =>
{
while (dr.Read())
{
...
}
});
Конечно же, присутствуют методы получения скалярного значения и старый-добрый Execute.
Добавление параметров для хранимки осуществляется вызовом AddParams
dm.AddParams(new { id = 10, name = "stringparam", writeDate = DateTime.Now }) ...
Так же реализован метод для передачи табличных параметров (Table-Valued Parameters) — конечно же он работает только для MS SQL Server начиная с 2008 версии.
dm.AddEnumerableParam("Details",
Enumerable.Range(1, 10)
.Select(e => new {id = e, name = string.Concat("Name", e.ToString())})
);
По производительности совсем немного отстаем от Dapper, особенно при певром вызове инициализатора объекта.
В планах — реализовать поддержку IQueryable-результатов с передачей параметров в хранимку, что очень пригодилось бы в ApiController'aх AspNet MVC.
Если кому то будет интересен данный велосипед, код библиотеки доступен на github.
Спасибо за внимание!