EF Bootstrapping
Привет всем любителям EF Code First.
Вчера на коленке написал конфигуратор для EF CF во fluent- стиле. Использование выглядит так:
var factory = Configure.WithContext<MyDbContext>("connectionstring") // использовать контекст типа MyDbContext и строку подключения connectionstring
.AddMappingsFromAssemblyOf<XXX>() // здесь есть перегрузки, принимающие Type и Assembly. В подключенной сборке находит все реализации EntityTypeConfiguration`1 и ComplexTypeConfiguration`1
.LazyLoadingEnabled(true) // пример конфигурации контекста
.BuildDbContextFactory(); // возвращается реализация IDbContextFactory
NHibernate 3.3
Пост для тех, кто пропустил релиз. В позапрошлую субботу (21 апреля 2012) вышел долгожданный (по-крайней мере для меня) релиз NHibernate 3.3.0.GA.
Скачать можно с SF или из NuGet
Список изменений на английском, можно почитать тут.
PS: я теперь в NH-core team ;)
PPS: не забывайте обо всех багах сообщать в баг-трекер.
nvarchar(?)
Всегда, когда работаю с базой данных и вижу вот такое:
CREATE TABLE [dbo].[Users] (
[UserId] int IDENTITY NOT NULL,
[EMail] nvarchar(215) NOT NULL,
)
задаю себе одни и те же вопросы:
- чем руководствовался автор, когда выбирал максимальную длину строки?
- Почему именно 215, а не 214 или 216?
У меня нет ответа на эти вопросы. Может быть вы знаете?
MvcExtensions - как подключить jquery-ui date picker в ASP.NET MVC. Unobtrusive way.
В Яндекс.Метрика подсмотрел, что по такому поисковому запросу приходят в мой блог и решил написать статью об этом. Задача довольно простая - позволить пользователю выбирать дату из красивого календаря, такого как jQuery UI Datepicker
Решение “в лоб”.
Как известно, ASP.NET MVC поддерживает шаблоны для редакторов полей. Первый вариант - использовать такой шаблон “в лоб”.
MvcExtensions - решение проблемы DropDown Lists
С самого выхода еще первой версии ASP.NET MVC три года назад я столкнулся с проблемой выпадающих списков. Наверное каждый из вас задавал себе вопрос: “Как корректно передавать данные для отображения в выпадающие списки?” Вот и меня до недавнего времени этот вопрос волновал и очень существенно. Я буквально не мог спать;)
Общая или личная база данных при разработке?
В этой статье я хочу рассказать о практиках работы разработчиков с базами данных в момент разработки продукта. Все из этих практик были опробованны мной лично и текст здесь основан на моем личном опыте.
Можно использовать единую для всех и индивидуальную базу данных для каждого разработчика. Оба подхода имеют как достоинства, так и недостатки.
ORM vs. Хранимые процедуры
Мой коллега, позавчера в очередной раз у себя в блоге поднял этот больной вопрос.
Для себя я уже давно определился с тем, какой подход использовать, и в этом посте я хочу рассказать вам почему я выбрал именно этот подход.
Хранимые процедуры
Защитники хранимых процедур приводят в пример два плюса их использования:
Хранимые процедуры компилируются и за счет этого исполняются быстрее, чем обычные SQL скрипты (как написанные вручную, так и сгенерированные ORM).
Если вам необходимо обработать много данных и вернуть мало результатов, то без хранимых процедур вам не обойтись.
Я не буду спорить с этими достоинствами, т.к. я с ними полностью согласен.
Но практически все защитники подхода с хранимыми процедурами молчат о недостатках:
RDBMS не выполняют контроль целостности кода хранимой процедуры при изменении схемы данных, от которых хранимая процедура зависит.
О том, что моя хранимая процедура сломалась я смогу узнать только при попытке ее выполнить. Но что, если уже поздно и версия попала к пользователям? Я думаю пользователи будут разочарованы.
Чтобы не быть голословным приведу пример (MS SQL Server):
тестовый скрипт.
вывод в консоль.
Обратите внимание, что процедура
sp_renameтолько предупреждает, что что-то может сломаться, но не говорит что именно. А если я просто вручную удалю колонку? Я об этом никогда не узнаю.Таким образом при использовании хранимых процедур мне необходимо держать в голове все зависимости между всеми объектами в базе данных и как можно быстрее распространять и актуализировать эти знания между членами команды, включая новобранцев. У последних, IMO, голова просто лопнет от переизбытка информации.
Хранимые процедуры отделены от клиентского кода, который их вызывает.
Если хранимая процедура была удалена, или переименована, или просто изменились её параметры об ошибках я узнаю только в момент попытки ее выполнения. Обычно хранимые процедуры используются в качестве средства устранения дублирования кода и, как следствие, вызываются разными клиентами. Т.е. в дополнение ко всем связям между объектами мне необходимо держать в голове информацию о всех клиентах и всех хранимых процедурах, которых они вызывают. В общем случае это ведет к тем же последствиям, что и в пункте 1.
Для того, чтобы посмотреть как ведет себя хранимая процедура, ее код, найти подходящую процедуру и т.п. мне необходимо открыть IDE для моей любимой RDBMS. Т.е. переключить контекст своего внимания. Иногда, когда я таким образом отвлекаюсь я очень долго не могу вернуться к работе.
Хранимые процедуры не переносимы между различными типами RDBMS.
Вы конечно можете сказать, что это чушь и, от части, будете правы, но только в том случае если вы делает корпоративный или уникальный продукт под заказ.
Но, очень часто продуктовые компании, разрабатывающие коробочные или близкие к ним кастомизируемые тиражируемые решения, попадают в зависимость от требования клиента: “Ваш продукт хорош, но он использует Oracle, а мы используем MS SQL и хотелось бы….” В этом случае есть 2,5 возможных развития сюжета: вы говорите, что это не возможно и теряете клиента; вы говорите, что нужно подождать полгода (год, два, …) и клиент уходит; или остается и вы судорожно начинаете искать специалиста по MS SQL, переписывать ваши хранимые процедуры и так далее.
При последнем, самом оптимистичном варианте, вам нужно найти дополнительных людей, иначе вы просто не сможете развивать уже существующую систему. При этом вам будет необходимо как-то достигать согласованности работы обоих систем. Я знаю команды, которые для этого написали свой SQL-подобный язык и транслятор к нему в поддерживаемые системы.
ORM
“Минусы” ORM:
ORM не всегда генерирует оптимальные запросы. Да, это так. Но, большинство современных ORM предоставляют пользователям возможность переключиться на уровень ближе к базе данных, вплоть до написания запросов на SQL и использования хранимых процедур.
К примеру NHibernate, которую я считаю лучшей, предоставляет следующие возможности для написания запросов:
- LINQ - самый высокоуровневый объектный подход к написанию запросов.
- CriteriaAPI или QueryOver - объектный, но SQL-ориентированный подход.
- HQL - hibernate query language - SQL-подобный язык для написания запросов.
- SQL - “голый” SQL
- Хранимые процедуры.
Также могу сказать, что кривость и неоптимальность запроса почти всегда напрямую зависит от кривости рук разработчика.
ORM тратит время на трансляцию запросов в SQL Да, но: это время не такое уж и большое; большинство ORM транслирует запрос в SQL только при первом выполнении запроса, затем они его кэшируют.
ORM - большие страшные монстры, для маленьких проектов, как “из пушки по воробьям”
На это могу возразить то, что существует множество мелких ORM. Так же можно использовать ORM напрямую и не заботится ни о каких DDD и прочих монструозных технологиях. К примеру, использование NHibernate + FluentNHibernate (автомаппинг) требует от разработчика написания 2х классов: 1. Конифигурацию NH; 2. Конфигурацию автомапинга. Причем эти классы могут быть практически без изменения кочевать из проекта в проект.
Плюсы ORM для меня (в порядке появления в голове):
ORM обеспечивает контроль целостности схемы данных
ОРМ позволяет работать с данными как с объектами. При этом обеспечивается контроль типов.
Некоторые ORM (EF, LLBLGen Pro) позволяют генерировать строго-типизированные обвертки, для вызова хранимых процедур.
Мне не нужно писать никакого кода на страшном ADO.NET.
Большинство ORM поддерживает несколько типов баз данных.
Применительно к NHibernate:
NHibernate поддерживает группировку запросов в пакеты и таким образом уменьшается количество обращений к RDBMS
NHibernate поддерживает отложенные запросы, которые выполнятся только в момент первого обращения к результатам. При этом все отложенные невыполненные запросы выполнятся в одном пакете.
Second Level Cache - очень мощный инструмент. В умелых руках, повышает производительность приложения в разы.
Заключение
ORM это не инструмент для замены хранимых процедур и запросов, это инструмент, для их дополнения. Он позволяет писать меньше рутинного однообразного кода - он уже написан за вас. Этот код уже протестирован множеством разработчиков на различных типах проектов. от мельчайших до сверх-гигантских.
Если в вашем проекте требуется мощь хранимых процедур, или скриптов написанных на SQL, то всегда используйте их вместе с, а не вместо ORM. Также я рекомендую пользоваться хранимыми процедурами только в исключительных ситуациях: только тогда, когда без них не обойтись.
Web.Requre - client script dependency framework
Что это?
Web.Require - небольшая библиотечка, для облегчения подключения скриптов в ваше Web-приложение.
Давай примеры!
<head>
<title>@ViewBag.Title</title>
@Html.RequireStyleSheet(
Url.Content("~/Content/reset.css"),
Url.Content("~/Content/Site.less"),
Url.Content("~/Content/themes/base/minified/jquery.ui.all.min.css"))
@Html.RequireScript(
Url.Content("~/Scripts/jquery-1.6.4.min.js"),
Url.Content("~/Scripts/jquery-ui-1.8.16.min.js"))
@Html.OutputRequiredStyleSheets()
@Html.OutputRequiredScripts()
</head>
Что тут происходит? Все очень просто: мы подключаем три css и два js файла. Что нам дает такой подход? Почему просто нельзя подключить скрипты в лоб?
Давайте представим, что какой-то контрол, например ваш кастомный диалог или, datepicker требует для своей работы отдельного скрпта? Как быть?
- Первый вариант - просто в контроле написать <script src=…/>. А если этот контрол на странице используется несколько раз?
- Второй вариант - просто включить этот скрипт в <head>. А если этот скрпит очень большой и прожорливый и используется не везде?
<!--DateTimePicker.cshtml-->В итоге DateTimePicker.js добавиться в тэг и наступит всеобщее счастье;)
@Html.RequireScript(
Url.Content("~/Scripts/jquery-1.6.4.min.js"),
Url.Content("~/Scripts/jquery-ui-1.8.16.min.js"),
Url.Content("~/Scripts/jquery.ui.datepicker-ru.js"),
Url.Content("~/Scripts/DateTimePicker.js"))
Как установить?
P.S.
В будущем ожидаются следующие фишки:- Асинхронное подключение js
- Конкатенация и минификация js и css + кэширование
P.P.S.
MvcExtensions: bootstrapping
Как я уже писал во введении, весь код, выполняющийся при старте приложения необходимо помещать в, BootstrapperTask.
Bootstrapper tasks
В MvcExtensions для размещения кода, выполняющегося при запуске приложения существует понятие Bootstrapper, которому можно назначать задачи.Назначение задачи происходит следующим образом:
//Global.asax.cs
public class MvcApplication : WindsorMvcApplication
{
public MvcApplication()
{
Bootstrapper.BootstrapperTasks
// задача, для регистрации контроллеров в IoC контейнер.
.Include();
}
}
Кроме стандартной задачи для регистрации контроллеров существует еще несколько стандартных задач, которые подключают дополнительные возможности. Также есть несколько базовых классов, которые позволяют выполняет такие полезные действия, как регистрация маршрутов, фильтров, модел-байндеров, etc.
Регистрация маршрутов
Для регистрации маршрутов вам необходимо унаследоваться от абстрактного класса RegisterRoutesBase, и подключить задачу к bootstrapper.
public class RegisterRoutes : RegisterRoutesBase
{
public RegisterRoutes(RouteCollection routes) : base(routes)
{
}
protected override void Register()
{
Routes.IgnoreRoute("favicon.ico");
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
Routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
}
Конфигурация фильтров
Еще одной интересной возможностью MvcExtensions является возможность добавлять фильтры (IMvcFilter) к действиям контроллеров динамически. Этот подход позволяет писать фильтры не через атрибуты, а как обычные классы, при этом появляется возможность использовать инжекцию зависимостей через конструктор. Для регистрации фильтров необходимо унаследоваться от базового абстрактного класса ConfigureFiltersBase.Рассмотрим небольшой пример:
public class ConfigureFilters : ConfigureFiltersBase
{
public ConfigureFilters(IFilterRegistry registry) : base(registry) { }
protected override void Configure()
{
Registry.Register<ProductController, PopulateCategories, PopulateSuppliers>(c => c.Create())
.Register<ProductController, PopulateCategories, PopulateSuppliers>(c => c.Edit(0));
}
}
Как видно из примера, для контроллера ProductController для действий Create и Edit регистрируются два фильтра PopulateCategories и PopulateSuppliers
Регистрация ModelBinder
В жизни бывает всякое и иногда необходимо написать свой ModelBinder, с помощью ConfigureModelBindersBase вы можете привязать свой ModelBinder к модели.public class ConfigureModelBinders : ConfigureModelBindersBase
{
public ConfigureModelBinders(TypeMappingRegistry<object, IModelBinder> registry)
: base(registry) { }
protected override void Configure()
{
Registry.Register<ProductEditModel, ProductEditModelBinder>();
}
}
Здесь вроде все понятно.
Собственные Bootstrapper tasks
Нет ничего проще, чем создать собственные задачи: необходимо просто унаследоваться от базового класса BootstrapperTask. Также задачи могут зависеть от других задач, для этого вашу задачу нужно пометить атрибутом [DependsOn]. И главное: не забудьте зарегистрировать свои задачи в бутстрапер.[DependsOn(typeof(RegisterRoutes))]
public class ConfigureFiltersBase : BootstrapperTask
{
protected override void Configure()
{
// Ваш код будет здесь
}
}