ASP.NET MVC - Entity Framework, MySQL e usando o Dependency Resolver para selecionar um repositório

Tecnologia herdada
Aviso: o ASP.NET MVC já está obsoleto. O uso do ASP.NET Core é recomendado. Mas se você estiver interessado, leia.

Decidi expandir um pouco o artigo anterior sobre o ASP.NET MVC e o MySQL . Tratava-se de trabalhar com o MySQL no ASP.NET MVC, não através do EF (ORM Entity Framework) quase padrão, mas usando o acesso direto ao DBMS através do ADO.NET. E foi dada uma implementação desse método de acesso. E embora o método seja obsoleto e não recomendado para uso, às vezes é útil: por exemplo, em aplicativos altamente carregados ou quando um desenvolvedor enfrenta uma situação em que o ORM não pode gerar uma consulta SQL funcionando corretamente. E às vezes você pode combinar os dois métodos em um aplicativo - através do ORM e do ADO.NET. Como resultado, pensei e decidi finalizar o aplicativo: adicionando uma implementação de repositório para o Entity Framework e fazer a escolha deles dependente do parâmetro do aplicativo usando o Dependency Resolver.

Todo o código pode ser obtido aqui neste endereço , abaixo deste código será parcialmente apresentado com pequenos links e explicações em relação ao projeto anterior . E aqui você pode ver o aplicativo .

Mudamos o projeto


1. Para usar o Entity Framework com MySQL, precisamos instalar a biblioteca MySQL.Data.EntityFramework (é claro que você pode ter outra, apenas a Oracle, proprietária do MySQL).


Ele puxará o MySQL.Data e o próprio EntityFramework . As seguintes alterações foram feitas no arquivo 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> 

Surgiu um conflito interessante com o MySQL.Data - já que o MySQL.Data.EntityFramework exigia uma versão do MySQL.Data de pelo menos 8.0.19, foi atualizada ... e o projeto parou de funcionar. Um erro começou a ocorrer:



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

Aparentemente, o assembly não assinado Ubiety.Dns.Core foi adicionado ao MySQL.Data 8.0.19 . Eu também tive que incluir esse componente através do Nuget no projeto. O erro se foi.

2. Além disso, para implementar a implementação de dependências, adicionamos ao projeto Ninject - o contêiner para implementação de dependências (DI).

3. Vamos mudar um pouco a estrutura do projeto: colocaremos os arquivos do repositório em um diretório Repository separado e criaremos os subdiretórios ADO.NET nele (transferiremos os arquivos LanguagesRepository.cs e UsersRepository.cs existentes ) e EF (haverá arquivos de repositório para o Entity Framework aqui).

4. Além disso, um parâmetro do aplicativo foi adicionado ao arquivo web.config na seção appConfig : <add key="ConnectionMethod" value="ADO.NET" /> . O aplicativo terá dois valores: "Entity Framework" ou "ADO.NET". Adicionado um link para esse parâmetro no arquivo Base.cs :

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

Entity Framework e MySQL - repositório


Adicione o arquivo EFDbContext com a classe EFDbContext ao EFDbContext Repository \ EF :

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

Nele, determinamos a cadeia de conexão DBMS usada e os conjuntos de dados Users e Languages .

Adicione o arquivo LanguagesRepositoryEF classe LanguagesRepositoryEF :

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

E o arquivo UsersRepository.cs com a 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; } } 

Pode-se ver que o tamanho do arquivo é claramente menor que o do ADO.NET - o ORM faz o trabalho sujo para nós - ele cria consultas SQL por conta própria.

No entanto, me deparei com alguns pontos que foram incluídos na implementação do ADO.NET, mas não funcionaram no EF.

A primeira coisa que fiz para alterar o arquivo UserClass.cs (no diretório Domínio ): adicione outro campo para a operação normal da conexão entre as tabelas Users e Languages :

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

E o segundo - descobriu-se que campos no MySQL, como o Enum , não funcionam com o EF. Provavelmente, a razão para isso é que a enumeração no código é um valor inteiro, mas no banco de dados os valores através do EF são lidos como texto (se em uma consulta do MySQL para ler os valores de um campo do tipo enum, o MySQL retorna apenas os valores de texto dessa enumeração). E se, na versão do ADO.NET, eu posso contornar isso usando a construção CAST(u.SupporterTier AS UNSIGNED) as SupporterTier , então, com a EF, essa metamorfose acabou por ser insuperável para mim - nenhuma das opções tentadas surgiu. Bem, como a tecnologia Code First gera um campo do tipo Enum como um campo do tipo INT, tivemos que alterar o tipo do campo SupporterTier no banco de dados:

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

Selecionar repositório usando o parâmetro do aplicativo


Usaremos a implementação por meio do construtor, exatamente como está escrito no livro. Primeiro, precisamos criar interfaces para nosso repositório compartilhado: crie o arquivo LanguagesRepository.cs no diretório Repository com o conteúdo:

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

E o arquivo UsersRepository.cs com o conteúdo:

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

Bem, herdamos as classes correspondentes dessas interfaces:

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

Bem, no controlador UsersController, fazemos adições que permitem trabalhar com estas interfaces:

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

E no controlador, repLanguages os locais de acesso aos objetos dessas classes para os repUsers e repUsers , respectivamente. Mas precisamos passar instâncias das classes de repositório através do construtor do controlador, o que, obviamente, é inconveniente. Para evitar isso, precisamos de feitiçaria forte, como o Dependency Resolver (DR). E para isso vamos usar o Ninject:

Registramos a DR no arquivo Global.asax.cs no método Application_Start :

 DependencyResolver.SetResolver(new NinjectDependencyResolver()); 

Crie o arquivo NinjectDependencyResolver.cs no diretório Infrastructure com a classe NinjectDependencyResolver (herdada da 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>(); } } } 

E acontece que o único local em que é determinado qual método de trabalho com o DBMS é usado (diretamente, através do ADO.NET ou do Entity Framework) é o método AddBindings na classe NinjectDependencyResolver . Magia real, se você não sabe como funciona.

No método AddBindings , dependendo do valor do parâmetro do aplicativo ConnectionMethod, o ILanguagesRepository e IUsersRepository com classes específicas que implementam os métodos de interface. Desde que NinjectDependencyResolver aplicativo, registramos a DR como um objeto da classe NinjectDependencyResolver e, na classe, especificamos a ligação das interfaces do repositório a uma classe específica, ao solicitar a estrutura MVC para criar o UsersController controlador UsersController , o Ninject ao analisar a classe descobrirá que é necessária a implementação das IUsersRepository e IUsersRepository criará instâncias de classes específicas e as passará para o construtor do controlador (via DR e a estrutura MVC).

Total


O aplicativo agora também suporta o método de acesso ao DBMS através do ORM Entity Framework. Ao mesmo tempo, o método de acesso através do ADO.NET não desapareceu e é selecionado quando o aplicativo é iniciado pelo parâmetro, para o qual usamos o método de injeção de dependência através do construtor do controlador usando a biblioteca Ninject.



PS E finalmente: você pode ver como este projeto funciona neste endereço . E aqui você pode baixar o projeto inteiro. Bem, para a pilha - um link para o meu blog .

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


All Articles