ASP.NET MVC: Entity Framework, MySQL y el uso de Dependency Resolver para seleccionar un repositorio

Tecnología heredada
Advertencia: ASP.NET MVC ya está en desuso. Se recomienda el uso de ASP.NET Core. Pero si estás interesado, entonces lee.

Decidí ampliar ligeramente el artículo anterior sobre ASP.NET MVC y MySQL . Se trataba de trabajar con MySQL en ASP.NET MVC no a través de ORM Entity Framework (EF) casi estándar, sino usando el acceso directo al DBMS a través de ADO.NET. Y se dio una implementación de este método de acceso. Y aunque el método está desactualizado y no se recomienda su uso, a veces es útil: por ejemplo, en aplicaciones altamente cargadas o cuando un desarrollador se enfrenta a una situación en la que ORM no puede generar una consulta SQL que funcione correctamente. Y a veces puede combinar ambos métodos en una aplicación, tanto a través de ORM como a través de ADO.NET. Como resultado, pensé y decidí terminar la aplicación: agregarle una implementación de repositorio para Entity Framework y hacer que la elección de ellos dependa del parámetro de la aplicación usando el Dependency Resolver.

Todo el código se puede tomar aquí en esta dirección , debajo de este código se presentará parcialmente con pequeños enlaces y explicaciones en relación con el proyecto anterior . Y aquí puedes ver la aplicación .

Cambiamos el proyecto


1. Para usar Entity Framework con MySQL, necesitamos instalar la biblioteca MySQL.Data.EntityFramework (por supuesto, puede tener otra, solo esta de Oracle, el propietario de MySQL).


Extraerá MySQL.Data y EntityFramework . Se realizaron los siguientes cambios en el archivo 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> 

Surgió un conflicto interesante con MySQL.Data , ya que MySQL.Data.EntityFramework requería una versión de MySQL.Data de al menos 8.0.19, se actualizó ... y el proyecto dejó de funcionar. Un error comenzó a ocurrir:



«Ubiety.Dns.Core» . . , , . ( HRESULT: 0x80131045)

Aparentemente, el ensamblado sin firmar Ubiety.Dns.Core se agregó a MySQL.Data 8.0.19 . También tuve que incluir este componente a través de Nuget en el proyecto. El error se ha ido.

2. Además, para implementar la implementación de dependencias, agregamos al proyecto Ninject: el contenedor para implementar dependencias (DI).

3. Cambiaremos ligeramente la estructura del proyecto: colocaremos los archivos del repositorio en un directorio de Repositorio separado y crearemos los subdirectorios ADO.NET en él (transferiremos los archivos existentes de LanguagesRepository.cs y UsersRepository.cs allí ) y EF (habrá archivos de repositorio para Entity Framework aquí).

4. Además, se ha agregado un parámetro de aplicación al archivo web.config en la sección appConfig : <add key="ConnectionMethod" value="ADO.NET" /> . La aplicación tomará dos valores: "Entity Framework" o "ADO.NET". Se agregó un enlace a este parámetro al archivo Base.cs :

 public static string ConnectionMethod { get { return System.Configuration.ConfigurationManager.AppSettings["ConnectionMethod"]; } } 

Entity Framework y MySQL - repositorio


Agregue el archivo DbContext.cs con la clase EFDbContext al EFDbContext Repository \ EF :

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

En él, determinamos la cadena de conexión DBMS utilizada y los conjuntos de datos de Users e Languages .

Agregue el archivo LanguagesRepository.cs con la clase LanguagesRepositoryEF :

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

Y el archivo UsersRepository.cs con la clase 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; } } 

Se puede ver que el tamaño del archivo es claramente más corto que el de ADO.NET - ORM hace el trabajo sucio por nosotros - crea consultas SQL por sí solo.

Sin embargo, me encontré con un par de puntos que llegaron a la implementación de ADO.NET, pero no funcionaron en EF.

Lo primero que tuve que hacer fue cambiar el archivo UserClass.cs (en el directorio de Dominio ): agregue otro campo para el funcionamiento normal de la conexión entre las tablas de Users e Languages :

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

Y el segundo: resultó que los campos en MySQL como Enum no funcionan a través de EF. Lo más probable es que la razón de esto sea que la enumeración en el código es un valor entero, pero desde la base de datos los valores a través de EF se leen como texto (si en una consulta de MySQL para leer los valores de un campo de tipo enum, MySQL devuelve solo los valores de texto de esta enumeración). Y si en la versión para ADO.NET puedo CAST(u.SupporterTier AS UNSIGNED) as SupporterTier esto usando CAST(u.SupporterTier AS UNSIGNED) as SupporterTier construcción de CAST(u.SupporterTier AS UNSIGNED) as SupporterTier , entonces con EF tal metamorfosis resultó ser insuperable para mí, ninguna de las opciones probadas surgió. Bueno, dado que la tecnología Code First genera un campo de tipo Enum como un campo de tipo INT, tuvimos que cambiar el tipo del campo SupporterTier en la base de datos:

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

Seleccione el repositorio usando el parámetro de la aplicación


Usaremos la implementación a través del constructor, tal como está escrito en el libro de texto. Primero, necesitamos crear interfaces para nuestro repositorio compartido: cree el archivo LanguagesRepository.cs en el directorio Repository con los contenidos:

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

Y el archivo UsersRepository.cs con el contenido:

 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); } 

Bueno, heredamos las clases correspondientes de estas interfaces:

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

Bueno, en el controlador UsersController, hacemos adiciones que le permitirán trabajar con estas interfaces:

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

Y en el controlador, cambiamos los lugares de acceso a los objetos de estas clases a los repUsers repLanguages y repUsers , respectivamente. Pero necesitamos pasar instancias de las clases de repositorio a través del constructor del controlador, lo que, por supuesto, es inconveniente. Para evitar esto, necesitamos hechicería fuerte como Dependency Resolver (DR). Y para esto usaremos Ninject:

Registramos DR en el archivo Global.asax.cs en el método Application_Start :

 DependencyResolver.SetResolver(new NinjectDependencyResolver()); 

Cree el archivo NinjectDependencyResolver.cs en el directorio Infraestructura con la clase NinjectDependencyResolver (heredada de la interfaz 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>(); } } } 

Y resulta que el único lugar en el que se determina qué método de trabajo con el DBMS se usa (directamente, a través de ADO.NET oa través del Entity Framework) es el método AddBindings en la clase NinjectDependencyResolver . Magia real si no sabes cómo funciona.

En el método AddBindings , dependiendo del valor del parámetro de aplicación ConnectionMethod, las IUsersRepository ILanguagesRepository e IUsersRepository con clases específicas que implementan los métodos de interfaz. Desde que NinjectDependencyResolver aplicación, registramos DR como un objeto de la clase NinjectDependencyResolver , y en la clase especificamos el enlace de las interfaces del repositorio a una clase específica, al solicitar el marco MVC para crear el UsersController controlador UsersController, Ninject al analizar la clase encontrará que requiere la implementación de las IUsersRepository ILanguagesRepository e IUsersRepository creará instancias de clases específicas y las pasará al constructor del controlador (a través de DR y el marco MVC).

Total


La aplicación ahora también admite el método de acceso al DBMS a través de ORM Entity Framework. Al mismo tiempo, el método de acceso a través de ADO.NET no ha desaparecido y se selecciona cuando el parámetro inicia la aplicación, para lo cual utilizamos el método de inyección de dependencia a través del constructor del controlador utilizando la biblioteca Ninject.



PD Y finalmente: puedes ver cómo funciona este proyecto en esta dirección . Y aquí puedes descargar todo el proyecto. Bueno, para el montón: un enlace a mi blog .

Source: https://habr.com/ru/post/484582/


All Articles