Микро-ORM одним классом

от автора

Здравствуйте, уважаемые хабравчане!

Хочу рассказать о собственной велонадстройке над 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *