J'ai récemment découvert que tout le monde qui travaille avec EF ne sait pas le cuisiner. De plus, ils ne sont pas désireux de comprendre. Faire face aux problÚmes au tout début - mise en place.
MĂȘme aprĂšs une configuration rĂ©ussie, il y a des problĂšmes avec les demandes de donnĂ©es. Non pas parce que les gens ne connaissent pas LINQ, mais parce que tout ne peut pas ĂȘtre mappĂ© des objets aux modĂšles relationnels. Parce qu'en travaillant avec un lien, les gens pensent dans des tableaux. Dessinez des requĂȘtes SQL et essayez de les traduire en LINQ.
Ceci et, peut-ĂȘtre, quelque chose d'autre dont je veux parler dans l'article.
Mise en place
Une personne est venue au projet, y a vu EF pour la premiÚre fois, s'est émerveillée d'un tel miracle, a appris à l'utiliser, a décidé de l'utiliser à des fins personnelles. Créer un nouveau projet, ... Et puis que faire?
Vous commencez à rechercher sur Google, à essayer, à faire des erreurs. Afin de simplement connecter EF à un projet, le développeur est confronté à des types de problÚmes incompréhensibles.
1. Et comment le configurer pour qu'il fonctionne avec le SGBD requis?
2. Et comment configurer le travail des migrations?
Je suis sûr qu'il y a plus de problÚmes, mais ce sont probablement les plus fréquents. Parlons de tout dans l'ordre.
1. Eh bien, voici Google. :) Votre tĂąche consiste Ă trouver un fournisseur EntityFramework Core pour votre SGBD.
Vous trouverez Ă©galement dans la description du fournisseur des instructions de configuration.
Pour PostgreSQL, par exemple, vous devez installer le package Nuget Npgsql.EntityFrameworkCore.PostgreSQL
Créez votre propre contexte en acceptant DbContextOptions et créez le tout comme ceci
var opts = new DbContextOptionsBuilder<MyDbContext>()
.UseNpgsql(constring);
var ctx = new MyDbContext(opts.Options);
Si vous disposez d'une application ASP .NET Core, le contexte est inscrit différemment dans le conteneur. Mais vous pouvez déjà le voir dans votre projet de travail. Ou google.
2. S'il n'y a pas de nounous dans votre entreprise, vous savez probablement comment installer les outils nécessaires pour travailler avec les migrations. Que ce soit pour un gestionnaire de packages ou NET Core CLI.
Mais ce que vous ne savez peut-ĂȘtre pas, c'est que le projet de dĂ©marrage sĂ©lectionnĂ© (--startup-project) est dĂ©marrĂ© lorsque vous travaillez avec des migrations. Cela signifie que si le projet de dĂ©part pour les migrations est le mĂȘme projet que celui qui lance votre application et lorsque vous dĂ©marrez pour une raison quelconque, vous lancez les migrations via ctx.Database.Migrate (), alors lorsque vous essayez de crĂ©er, supprimez une migration prĂ©cĂ©demment crĂ©Ă©e ou en crĂ©ez une autre, alors la derniĂšre migration crĂ©Ă©e sera transfĂ©rĂ©e vers la base. SURPRISE!
Mais, peut-ĂȘtre, en essayant de crĂ©er la premiĂšre migration, vous attraperez quelque chose comme ça
Aucun fournisseur de base de donnĂ©es n'a Ă©tĂ© configurĂ© pour ce DbContext. Un fournisseur peut ĂȘtre configurĂ© en remplaçant la mĂ©thode DbContext.OnConfiguring ou en utilisant AddDbContext sur le fournisseur de services d'application. Si AddDbContext est utilisĂ©, assurez-vous Ă©galement que votre type DbContext accepte un objet DbContextOptions <TContext> dans son constructeur et le transmet au constructeur de base pour DbContext.
En effet, l'outil qui fonctionne avec les migrations les crée en fonction de votre contexte, mais pour cela, il a besoin d'une instance. Et pour le fournir, vous devez implémenter l'interface IDesignTimeDbContextFactory dans le projet de démarrage.Ce n'est pas difficile, il n'y a qu'une seule méthode qui devrait renvoyer une instance de votre contexte.
Configuration des modĂšles
Vous avez déjà créé votre premiÚre migration, mais pour une raison quelconque, elle est vide. Bien que vous ayez des modÚles.
Le fait est que vous n'avez pas appris votre contexte pour traduire vos modÚles dans votre base de données.
Pour qu'EF crée une migration afin de créer une table pour votre modÚle dans la base de données, vous devez au moins créer une propriété du type DbSet <MyEntity> dans votre contexte.
par exemple, par cette propriété
publique DbSet <MyEntity> MyEntities {get; ensemble; }
La table MyEntities sera créée avec des champs correspondant aux propriétés de l'entité MyEntity.
Si vous ne souhaitez pas avoir de DbSet ou si vous souhaitez influencer les rÚgles de création de table par défaut pour une entité, vous devez remplacer
protected override void OnModelCreating(ModelBuilder modelBuilder)
la méthode de votre contexte.
Comment faire cela, vous le savez probablement. De plus, vous connaissez probablement les attributs qui vous permettent de contrÎler les rÚgles de mappage. Mais voici le truc. Les attributs ne donnent pas un contrÎle complet sur le mappage, ce qui signifie que vous aurez des améliorations dans OnModelCreating. Autrement dit, vous aurez des rÚgles pour mapper les entités à la fois sous la forme d'annotations et sous la forme d'une API fluide. Allez chercher alors pourquoi le champ a le mauvais nom que vous attendiez, ou les mauvaises restrictions.
- Eh bien, tout est simple, - dites-vous - je vais tout configurer via OnModelCreating
Et puis, aprÚs avoir configuré la 20e entité à partir de 10 champs, vos yeux commencent à onduler. Vous essayez de trouver le réglage sur le terrain pour une entité, mais tout flotte devant vos yeux à partir d'un bloc uniforme de 200 à 500 lignes de long.
Oui, vous pouvez diviser ce bloc en 20 méthodes et cela deviendra un peu plus facile. Mais il est utile de savoir qu'il existe une interface IEntityTypeConfiguration <TEntity>, en implémentant laquelle pour une entité spécifique, vous pouvez décrire dans cette implémentation les rÚgles de mappage pour une entité spécifique. Eh bien, pour que le contexte le récupÚre, dans OnModelCreating, vous devez écrire
modelBuilder.ApplyConfigurationsFromAssembly (assemblyWithConfigurations);
Et, bien sĂ»r, il est possible de transfĂ©rer le comportement gĂ©nĂ©ral vers le mĂȘme OnModelCreating. Par exemple, si vous avez une rĂšgle gĂ©nĂ©rale pour les identifiants de toutes ou de nombreuses entitĂ©s, vous pouvez la configurer comme ceci
foreach (var idEntity in modelBuilder.Model.GetEntityTypes()
.Where(x => typeof(BaseIdEntity).IsAssignableFrom(x.ClrType))
.Select(x => modelBuilder.Entity(x.ClrType)))
{
idEntity.HasKey(nameof(BaseIdEntity.Id));
}
CrĂ©er des requĂȘtes
Bon, on a mis les choses en ordre, c'est devenu un peu plus agréable.
Reste maintenant Ă refaire les requĂȘtes de notre projet home de SQL vers Linq. J'ai dĂ©jĂ Ă©crit des requĂȘtes au travail, c'est aussi simple que de
décortiquer des poires ctx.MyEntities.Where (condition) .Select (map) .GroupBy (expression) .OrderBy (expression)⊠facilité.
Alors, quelle est notre demande?
SELECT bla bla bla FROM table
RIGHT JOIN.......
Ouais
ctx.LeftEntities.RightJoin(.... f@#$
J'utilise Linq et ses mĂ©thodes d'extension bien avant de connaĂźtre EF. Et je n'ai jamais eu une seule question, mais oĂč est RIGHT JOIN, LEFT JOIN dedans ...
Qu'est-ce que LEFT JOIN du point de vue des objets?
il
class LeftEntity
{
public List<RightEntity> RightEntities { get; set; }
}
Ce n'est mĂȘme pas magique. Nous avons juste une certaine entitĂ© qui fait rĂ©fĂ©rence Ă une liste d'autres entitĂ©s, mais qui peuvent ne pas en avoir.
C'est ça
ctx.LeftEntities.Include(x => x.RightEntities)
Qu'est-ce que RIGHT JOIN? Ceci est une jointure gauche inversée, ce qui signifie que nous commençons simplement avec une entité différente.
Mais tout se passe, parfois vous avez besoin de contrĂŽler chaque bundle, en tant qu'entitĂ© distincte, mĂȘme si l'entitĂ© de gauche n'est pas associĂ©e Ă une seule entitĂ© de droite (celle de droite est NULL). Par consĂ©quent, explicitement LEFT JOIN peut ĂȘtre fait comme ceci
ctx.LeftEntities.SelectMany(x => x.RightEntities.DefaultIfEmpty(), (l, r) => new { Left = l, Right = r })
Cela nous donne le contrÎle sur la liaison, comme dans le modÚle relationnel. Pourquoi pourriez-vous en avoir besoin? Disons qu'un client a besoin d'afficher des données sous la forme d'un tableau et qu'un tri et / ou une pagination sont nécessaires.
Je n'aime pas non plus cette idée, mais chacun a ses propres bizarreries et son amour sans fin dans Excel. Nous allons donc couvrir le tapis avec une toux, jurant dans un poing et continuer à travailler. Quelle est la suite pour nous?
FULL JOIN
Alors, bon, j'ai déjà compris, on ne pense pas avec SQL, on pense avec des objets, comme ça, et maintenant comme ça ... bon sang. Comment décrire cela?
Sans tuples, nulle part.
En gĂ©nĂ©ral, lorsque nous devons travailler avec des tuples, nous perdons la comprĂ©hension du type de connexion (1-1, 1-n, nn) ââet en gĂ©nĂ©ral, thĂ©oriquement, nous pouvons croiser des mouches et des Ă©lĂ©phants. C'est de la magie noire.
Faisons le!
Prenons plusieurs Ă plusieurs comme base.
Ainsi, nous avons 3 types d'entité
LeftEntity
RightEntity
LeftRight (pour organiser la connexion)
LeftRight que je vais créer avec une clé composite. Je n'ai pas besoin d'un accÚs direct à cette entité, les références externes ne sont pas nécessaires, donc je ne produirai pas de champs et d'indices inutiles.
Ă la suite de la requĂȘte, je souhaite obtenir un ensemble de tuples, qui contiendra l'entitĂ© de gauche et l'entitĂ© de droite. De plus, si l'entitĂ© vierge n'a rien Ă voir avec la bonne, alors le bon objet sera nul. Il en va de mĂȘme pour les entitĂ©s de droite.
Il s'avÚre que LeftRight ne nous convient pas en sortie, ses limitations ne nous permettront pas de créer un bundle sans l'une des entités. Si vous pratiquez le DDD, vous découvrirez des incohérences.
MĂȘme si vous ne pratiquez pas, vous ne devriez pas. CrĂ©ons un nouveau type pour la sortie
LeftRightFull, qui contient toujours une référence aux entités gauche et droite.
Donc, nous avons
Gauche
L1
L2
Droite
R1
R2
Gauche Droite
L2 R2
Nous voulons la sortie
L1 n
L2 R2
n R1
Commençons par la connexion de gauche
var query = ctx.LeftEntities
.SelectMany(x => x.RightLinks.DefaultIfEmpty(), (l, lr) => new LeftRightFull
{
LeftEntity = l,
RightEntity = lr.RightEntity
})
Maintenant, nous avons dĂ©jĂ
L1 n
L2 R2
Quelle est la prochaine Ă©tape? Stick RightEntity via SelectMany? Eh bien, nous obtenons donc
L1 n R1 n
L1 n R2 L2
L2 R2 R1 n
L2 R2 R2 L2
Nous pouvons filtrer les éléments inutiles (L2 R2 R1 n et L1 n R2 L2) par condition, cependant, on ne sait pas comment transformer L1 n R1 n en
L1 n
n R1
Peut-ĂȘtre que les fouilles nous ont conduits dans la mauvaise steppe. Connectons simplement ces requĂȘtes similaires avec des jointures gauche pour les entitĂ©s gauche et droite via Union.
L1 n
L2 R2
UNION
L2 R2
n R1
Ici, vous pouvez avoir des questions sur les performances. Je n'y répondrai pas, car tout dépendra du SGBD spécifique et de la minutie de son optimiseur.
Vous pouvez Ă©galement crĂ©er une vue avec la requĂȘte souhaitĂ©e. Union ne fonctionne pas sur EF Core 2, j'ai donc dĂ» crĂ©er une vue. Cependant, je recommande de ne pas l'oublier. Soudainement, vous dĂ©cidez d'implĂ©menter la suppression logicielle des entitĂ©s via l'indicateur IsDeleted. Dans la vue, vous devez prendre en charge cela. Si vous oubliez, on ne sait pas quand vous recevrez un rapport de bogue.
Au fait, comment implĂ©menter la suppression logicielle sans rĂ©Ă©crire tout le code? J'en parlerai la prochaine fois. Peut-ĂȘtre autre chose.
Au revoir Ă tout le monde.