Хочу рассказать о собственной велонадстройке над 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.
Спасибо за внимание!
ссылка на оригинал статьи http://habrahabr.ru/post/218225/
Добавить комментарий