Хабрахабр

ASP.NET MVC — Entity Framework, MySQL и использование Dependency Resolver для выбора репозитория

Legacy технологии

NET MVC уже устарел. Предупреждение: ASP. NET Core. Рекомендуется использовать ASP. Но если вам интересно, то читайте.

Решил немного расширить предыдущую статью про ASP.NET MVC и MySQL. В ней речь шла про работу с MySQL в ASP.NET MVC не через практически стандартный ORM Entity Framework (EF), а с помощью прямого доступа к СУБД через ADO.NET. И была приведена реализация этого метода доступа. И хотя метод устаревший и не рекомендуемый к использованию, но иногда полезен: например, в высоконагруженных приложениях или когда разработчик сталкивается с ситуацией, когда ORM не может сгенерировать корректно работающий SQL-запрос. И иногда можно совмещать в приложении оба способа — и через ORM и через ADO.NET. В итоге я подумал, и решил дописать приложение: добавив в него реализацию репозитория для Entity Framework и сделать выбор из них зависимым от параметра приложения с помощью Dependency Resolver.

А здесь можно посмотреть на работу приложения.
Весь код можно взять вот по этому адресу, ниже этот код будет частично презентован с небольшими ссылками и пояснениями по отношению к предыдущему проекту.

Изменяем проект

1. Для использования Entity Framework с MySQL мы должны установить библиотеку MySQL.Data.EntityFramework (можно, конечно, и другую, просто эта от Oracle — владельца MySQL).

Data и собственно EntityFramework.
Она потянет за собой MySQL. В файл web.config внесены изменения:

<entityFramework> <providers> <provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.EntityFramework, Version=8.0.19.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" /> </providers>
</entityFramework>

С MySQL.Data возникла интересная коллизия — поскольку MySQL.Data.EntityFramework потребовал версии MySQL.Data не ниже 8.0.19, то он обновился… и проект перестал работать. Стала возникать ошибка:

Dns. Не удалось загрузить файл или сборку «Ubiety. Невозможно проверить подпись строгого имени. Core» либо одну из их зависимостей. (Исключение из HRESULT: 0x80131045) Возможно, сборка была изменена или построена с отложенной подписью, но не полностью подписана правильным закрытым ключом.

Data 8. Видимо в версию MySQL. 19 была добавлена не подписанная сборка Ubiety. 0. Core. Dns. Ошибка пропала. Пришлось в проект ещё и включить этот компонент через Nuget.

Кроме этого для реализации внедрения зависимостей добавим в проект Ninject — контейнер внедрения зависимостей (DI). 2.

Немного изменим структуру проекта: файлы репозитория вынесем в отдельный каталог Repository и создадим в нём еще подкаталоги ADO. 3. NET (перенесём туда имеющиеся файлы LanguagesRepository.cs и UsersRepository.cs) и EF (тут будут файлы репозитория для Entity Framework).

Кроме этого в файл web.config в раздел appConfig добавлен параметр приложения: <add key="ConnectionMethod" value="ADO. 4. Приложение будет принимать два значения: «Entity Framework» или «ADO. NET" />. В файл Base.cs добавил ссылку на этот параметр: NET».

public static string ConnectionMethod

}

Entity Framework и MySQL – репозитарий

Добавим в каталог Repository\EF файл DbContext.cs с классом EFDbContext:

public class EFDbContext : DbContext { public EFDbContext() : base(Base.ConnectionString) { } public DbSet<UserClass> Users { get; set; } public DbSet<LanguageClass> Languages { get; set; }
}

В нём мы определяем используемую строку подключения к СУБД и наборы данных Users и Languages.

Добавляем файл LanguagesRepository.cs с классом LanguagesRepositoryEF:

public class LanguagesRepositoryEF : ILanguagesRepository
{ private EFDbContext context = new EFDbContext(); public IList<LanguageClass> List() { return context.Languages.OrderBy(x => x.LanguageName).ToList(); }
}

И файл UsersRepository.cs с классом UsersRepositoryEF:

public class UsersRepositoryEF : IUsersRepository
{ private EFDbContext context = new EFDbContext(); public IList<UserClass> List() { return context.Users.ToList(); } public IList<UserClass> List(string sortName, SortDirection sortDir, int page, int pageSize, out int count) { count = context.Users.Count(); if (sortName != null) return context.Users.OrderByDynamic(sortName, sortDir).Skip((page - 1) * pageSize).Take(pageSize).ToList(); else return context.Users.OrderBy(o => o.UserID).Skip((page - 1) * pageSize).Take(pageSize).ToList(); } public bool AddUser(UserClass user) { user.Language = context.Languages.Find(user.Language.LanguageID); if (user.Language != null && context.Users.Add(user) != null) { try { context.SaveChanges(); } catch (System.Exception ex) {} } return user.UserID > 0; } public UserClass FetchByID(int userID) { UserClass user = null; try { user = context.Users.Find(userID); } catch (System.Exception ex) { } return user; } public bool ChangeUser(UserClass user) { bool result = false; user.Language = context.Languages.Find(user.Language.LanguageID); if (user.Language != null) { UserClass olduser = context.Users.Find(user.UserID); if (olduser != null) { olduser.Email = user.Email; olduser.Loginname = user.Loginname; olduser.Language = user.Language; olduser.SupporterTier = user.SupporterTier; try { result = context.SaveChanges() > 0; } catch (System.Exception ex) { } } } return result; } public bool RemoveUser(UserClass user) { bool result = false; UserClass olduser = context.Users.Find(user.UserID); if (olduser != null) context.Users.Remove(olduser); try { result = context.SaveChanges() > 0; } catch (System.Exception ex) { } return result; }
}

Видно, что размер файла явно короче подобного для ADO.NET — ORM делает за нас «грязную» работу — создает SQL-запросы самостоятельно.

NET, но не работают в EF. Однако, я столкнулся с парой моментов, которые прокатывали в реализации ADO.

Первый, что пришлось внести изменение в файл UserClass.cs (в каталоге Domain): добавить еще одно поле для нормальной работы связи таблиц Users и Languages:

[HiddenInput(DisplayValue = false)]
public int? LanguageID { get; set; }

И второй — оказалось что поля в MySQL типа Enum не работают через EF. Скорее всего причина этого в том, что перечисление в коде является целочисленным значением, а вот из БД значения через EF читаются как текст (если в запросе из MySQL читать значения поля типа enum MySQL возвращает как раз текстовые значения этого перечисления). И если в версии для ADO.NET я могу это обойти с помощью конструкции CAST(u.SupporterTier AS UNSIGNED) as SupporterTier, то с EF такая метаморфоза оказалась для меня непреодолимой — ни один из пробуемых вариантов не подошёл. Ну и поскольку технология Code First поле типа Enum генерирует в виде поля типа INT, то пришлось в БД поменять тип поля SupporterTier:

CHANGE COLUMN `SupporterTier` `SupporterTier` INT(4) UNSIGNED NOT NULL DEFAULT '1' ;

Выбор репозитория с помощью параметра приложения

Воспользуемся внедрением через конструктор, прямо как написано в учебнике. Во-первых, нам надо создать интерфейсы для нашего общего репозитария: создаем файл LanguagesRepository.cs в каталоге Repository с содержимым:

public interface ILanguagesRepository
{ IList<LanguageClass> List();
}

И файл UsersRepository.cs с содержимым:

public interface IUsersRepository
{ IList<UserClass> List(); IList<UserClass> List(string sortName, SortDirection sortDir, int page, int pageSize, out int count); bool AddUser(UserClass user); UserClass FetchByID(int userID); bool ChangeUser(UserClass user); bool RemoveUser(UserClass user);
}

Ну и наследуем соответствующие классы от этих интерфейсов:

public class LanguagesRepositoryADO : ILanguagesRepository
public class UsersRepositoryADO : IUsersRepository
public class LanguagesRepositoryEF : ILanguagesRepository
public class UsersRepositoryEF : IUsersRepository

Ну и в контроллер UsersController вносим добавления, которые позволят ему работать с этими интерфейсами:

private ILanguagesRepository repLanguages;
private IUsersRepository repUsers; public UsersController(ILanguagesRepository langsParam, IUsersRepository usersParam) { repLanguages = langsParam; repUsers = usersParam;
}

И в контроллере изменяем места обращения к объектам этих классов на объекты repLanguages и repUsers, соответственно. Но нам потребуется передавать экземпляры классов репозиториев через конструктор контроллера, что, конечно, неудобно. Чтобы этого избежать, нам нужно сильное колдунство типа Dependency Resolver (DR). И для этого мы будем использовать Ninject:

Регистрируем DR в файле Global.asax.cs в методе Application_Start:

DependencyResolver.SetResolver(new NinjectDependencyResolver());

Создадим файл NinjectDependencyResolver.cs в каталоге Infrastructure с классом NinjectDependencyResolver (унаследовавшего от интерфейса IDependencyResolver):

public class NinjectDependencyResolver : IDependencyResolver { private IKernel kernel; public NinjectDependencyResolver() { kernel = new StandardKernel(); AddBindings(); } public object GetService(Type serviceType) { return kernel.TryGet(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { return kernel.GetAll(serviceType); } private void AddBindings() { if (Domain.Base.ConnectionMethod == "Entity Framework") { kernel.Bind<ILanguagesRepository>().To<LanguagesRepositoryEF>(); kernel.Bind<IUsersRepository>().To<UsersRepositoryEF>(); } else { kernel.Bind<ILanguagesRepository>().To<LanguagesRepositoryADO>(); kernel.Bind<IUsersRepository>().To<UsersRepositoryADO>(); } } }

И получается, что единственное место, в котором определяеся какой метод работы с СУБД используется (напрямую, через ADO.NET или через Entity Framework) это метод AddBindings в классе NinjectDependencyResolver. Настоящая магия, если не знать как это работает.

Поскольку при старте приложения мы зарегистрировали DR как объект класса NinjectDependencyResolver, а в классе мы указали привязку интерфейсов репозиториев к конкретному классу, то при запросе фреймворка MVC на создание объекта контроллера UsersController, Ninject при анализе класса обнаружит, что он требует реализацию интерфейсов ILanguagesRepository и IUsersRepository и создаст экземпляры конкретных классов и передаст их в конструктор контроллера (через DR и фрейворк MVC). В методе AddBindings в зависимости от значения параметра приложения «ConnectionMethod» происходит связка интерфейсов ILanguagesRepository и IUsersRepository с конкретными классами реализующими методы интерфейсов.

Итого

Приложение теперь поддерживает и метод доступа к СУБД через ORM Entity Framework. При этом метод доступа через ADO.NET никуда не делся и выбирается при запуске приложения по параметру, для чего было использован метод внедрения зависимости через конструктор контроллера с помощью библиотеки Ninject.

S. P. А вот тут можно скачать весь проект. И напоследок: посмотреть, как работает данный проект можно по этому адресу. Ну и до кучи — ссылка на мой блог.

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть