ASP.NET MVC - Entity Framework, MySQL et utilisation de Dependency Resolver pour sélectionner un référentiel

Technologie héritée
Avertissement: ASP.NET MVC est déjà obsolète. L'utilisation d'ASP.NET Core est recommandée. Mais si vous êtes intéressé, alors lisez.

J'ai décidé d'étendre légèrement l' article précédent sur ASP.NET MVC et MySQL . Il s'agissait de travailler avec MySQL dans ASP.NET MVC non pas via le cadre d'entité ORM (EF) presque standard, mais en utilisant un accès direct au SGBD via ADO.NET. Et une implémentation de cette méthode d'accès a été donnée. Et bien que la méthode soit obsolète et déconseillée, elle est parfois utile: par exemple, dans des applications très chargées ou lorsqu'un développeur est confronté à une situation où ORM ne peut pas générer une requête SQL fonctionnant correctement. Et parfois, vous pouvez combiner les deux méthodes dans une application - à la fois via ORM et via ADO.NET. En conséquence, j'ai pensé et décidé de terminer l'application: en y ajoutant une implémentation de référentiel pour Entity Framework et en faire le choix en fonction du paramètre de l'application à l'aide du résolveur de dépendances.

Tout le code peut être pris ici à cette adresse , ci-dessous ce code sera partiellement présenté avec de petits liens et explications par rapport au projet précédent . Et ici, vous pouvez voir l'application .

Nous changeons le projet


1. Pour utiliser Entity Framework avec MySQL, nous devons installer la bibliothèque MySQL.Data.EntityFramework (vous pouvez bien sûr en avoir une autre, juste celle d'Oracle, le propriétaire de MySQL).


Il tirera lui-même MySQL.Data et EntityFramework . Les modifications suivantes ont été apportées au fichier 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> 

Un conflit intéressant est survenu avec MySQL.Data - puisque MySQL.Data.EntityFramework nécessitait une version de MySQL.Data d' au moins 8.0.19, elle a été mise à jour ... et le projet a cessé de fonctionner. Une erreur a commencé à se produire:



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

Apparemment, l' assembly non signé Ubiety.Dns.Core a été ajouté à MySQL.Data 8.0.19 . J'ai également dû inclure ce composant via Nuget dans le projet. L'erreur a disparu.

2. De plus, pour implémenter l'implémentation des dépendances, nous ajoutons au projet Ninject - le conteneur pour l'implémentation des dépendances (DI).

3. Nous allons légèrement modifier la structure du projet: nous mettrons les fichiers de référentiel dans un répertoire de référentiel séparé et y créerons les sous-répertoires ADO.NET (nous y transférerons les fichiers LanguagesRepository.cs et UsersRepository.cs existants) et EF (il y aura ici des fichiers de référentiel pour Entity Framework).

4. De plus, un paramètre d'application a été ajouté au fichier web.config dans la section appConfig : <add key="ConnectionMethod" value="ADO.NET" /> . L'application prendra deux valeurs: "Entity Framework" ou "ADO.NET". Ajout d'un lien vers ce paramètre dans le fichier Base.cs :

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

Entity Framework et MySQL - référentiel


Ajoutez le fichier DbContext.cs avec la classe EFDbContext au EFDbContext Repository \ EF :

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

Dans celui-ci, nous déterminons la chaîne de connexion SGBD utilisée et les ensembles de données Users et Languages .

Ajoutez le fichier LanguagesRepository.cs avec la classe LanguagesRepositoryEF :

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

Et le fichier UsersRepository.cs avec la classe 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; } } 

On peut voir que la taille du fichier est nettement plus courte que celle d'ADO.NET - ORM fait le sale boulot pour nous - il crée lui-même des requêtes SQL.

Cependant, je suis tombé sur quelques points qui ont été intégrés à l'implémentation d'ADO.NET, mais qui n'ont pas fonctionné dans EF.

La première chose que j'ai dû faire un changement dans le fichier UserClass.cs (dans le répertoire Domain ): ajoutez un autre champ pour le fonctionnement normal de la connexion entre les tables Users et Languages :

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

Et le second - il s'est avéré que les champs de MySQL tels que Enum ne fonctionnent pas via EF. Très probablement, la raison en est que l'énumération dans le code est une valeur entière, mais à partir de la base de données, les valeurs via EF sont lues sous forme de texte (si dans une requête de MySQL pour lire les valeurs d'un champ de type enum, MySQL renvoie uniquement les valeurs textuelles de cette énumération). Et si dans la version pour ADO.NET je peux contourner cela en utilisant CAST(u.SupporterTier AS UNSIGNED) as SupporterTier construction CAST(u.SupporterTier AS UNSIGNED) as SupporterTier , alors avec EF une telle métamorphose s'est avérée insurmontable pour moi - aucune des options essayées n'est apparue. Eh bien, puisque la technologie Code First génère un champ de type Enum comme champ de type INT, nous avons dû changer le type du champ SupporterTier dans la base de données:

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

Sélectionnez le référentiel à l'aide du paramètre d'application


Nous utiliserons l'implémentation via le constructeur, tout comme il est écrit dans le manuel. Tout d'abord, nous devons créer des interfaces pour notre référentiel partagé: créez le fichier LanguagesRepository.cs dans le répertoire Repository avec le contenu:

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

Et le fichier UsersRepository.cs avec le contenu:

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

Eh bien, nous héritons des classes correspondantes de ces interfaces:

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

Eh bien, dans le contrôleur UsersController, nous faisons des ajouts qui lui permettront de fonctionner avec ces interfaces:

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

Et dans le contrôleur, nous changeons les endroits d'accès aux objets de ces classes aux repUsers repLanguages et repUsers , respectivement. Mais nous devons passer des instances des classes de référentiel via le constructeur du contrôleur, ce qui, bien sûr, n'est pas pratique. Pour éviter cela, nous avons besoin d'une sorcellerie puissante comme le résolveur de dépendances (DR). Et pour cela, nous utiliserons Ninject:

Nous enregistrons DR dans le fichier Global.asax.cs dans la méthode Application_Start :

 DependencyResolver.SetResolver(new NinjectDependencyResolver()); 

Créez le fichier NinjectDependencyResolver.cs dans le répertoire Infrastructure avec la classe NinjectDependencyResolver (hérité de l'interface 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>(); } } } 

Et il s'avère que le seul endroit où il est déterminé quelle méthode de travail avec le SGBD est utilisée (directement, via ADO.NET ou via Entity Framework) est la méthode AddBindings dans la classe NinjectDependencyResolver . De la vraie magie si vous ne savez pas comment cela fonctionne.

Dans la méthode AddBindings , selon la valeur du paramètre d'application ConnectionMethod, les IUsersRepository ILanguagesRepository et IUsersRepository avec des classes spécifiques qui implémentent les méthodes d'interface. Depuis que nous avons NinjectDependencyResolver application, nous avons enregistré DR en tant qu'objet de la classe NinjectDependencyResolver , et dans la classe, nous avons spécifié la liaison des interfaces de référentiel à une classe spécifique, lorsque nous UsersController au framework MVC de créer l' UsersController contrôleur UsersController, Ninject lors de l'analyse de la classe constatera qu'il nécessite la mise en œuvre des IUsersRepository ILanguagesRepository et IUsersRepository et créera des instances de classes spécifiques et les transmettra au constructeur du contrôleur (via DR et le framework MVC).

Total


L'application prend désormais également en charge la méthode d'accès au SGBD via ORM Entity Framework. Dans le même temps, la méthode d'accès via ADO.NET n'a pas disparu et est sélectionnée lorsque l'application est lancée par le paramètre, pour lequel nous avons utilisé la méthode d'injection de dépendance via le constructeur du contrôleur à l'aide de la bibliothèque Ninject.



PS Et enfin: vous pouvez voir comment ce projet fonctionne à cette adresse . Et ici, vous pouvez télécharger l'intégralité du projet. Eh bien, au tas - un lien vers mon blog .

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


All Articles