Je modifie mon application pour pouvoir spécifier les propriétés de navigation à charger dans le référentiel.
Modèle: Team et TeamTunerUser se trouvent dans le domain entites .
Repository:
private static void ConfigureTeamTunerUser(EntityTypeBuilder<TeamTunerUser> builder) { ConfigureDescriptiveEntity(builder); builder.HasMany(e => e.CardLevels) .WithOne(e => e.User); builder.HasOne(e => e.Team) .WithMany(e => e.Users); // Indexes and unique constraint builder.HasIndex(e => e.Name) .IsUnique(); builder.HasIndex(e => e.SppdName) .IsUnique(); builder.HasIndex(e => e.Email) .IsUnique(); }
Test:
protected IQueryable<TEntity> GetQueryableWithIncludes(IEnumerable<string> includeProperties = null) { var queryable = Set; if (includeProperties == null) { return queryable; } if (typeof(TEntity) == typeof(Team)) { // TODO: Remove this block once it works by including by string properties foreach (var propertyName in includeProperties) { if (propertyName == "Users") { queryable.OfType<Team>().Include(entity => entity.Users); } } } else { foreach (var propertyName in includeProperties) { queryable.Include(propertyName); } } return queryable; }
Mon problème est que la propriété de navigation Users
ne se charge jamais et que le deuxième bloc d'assertion échoue.
L'équipe est configurée ici ( class ):
2019-04-11 16:02:43,896 [14] DEBUG Microsoft.EntityFrameworkCore.Database.Connection - Opening connection to database 'Sppd.TeamTuner-TEST' on server '.\SQLEXPRESS'. 2019-04-11 16:02:43,901 [14] DEBUG Microsoft.EntityFrameworkCore.Database.Connection - Opened connection to database 'Sppd.TeamTuner-TEST' on server '.\SQLEXPRESS'. 2019-04-11 16:02:43,903 [14] DEBUG Microsoft.EntityFrameworkCore.Database.Command - Executing DbCommand [Parameters=[@__entityId_0='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30'] SELECT TOP(2) [m].[Id], [m].[Avatar], [m].[CreatedById], [m].[CreatedOnUtc], [m].[DeletedById], [m].[DeletedOnUtc], [m].[Description], [m].[FederationId], [m].[IsDeleted], [m].[ModifiedById], [m].[ModifiedOnUtc], [m].[Name] FROM [Team] AS [m] WHERE ([m].[IsDeleted] = 0) AND ([m].[Id] = @__entityId_0) 2019-04-11 16:02:43,920 [12] INFO Microsoft.EntityFrameworkCore.Database.Command - Executed DbCommand (16ms) [Parameters=[@__entityId_0='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30'] SELECT TOP(2) [m].[Id], [m].[Avatar], [m].[CreatedById], [m].[CreatedOnUtc], [m].[DeletedById], [m].[DeletedOnUtc], [m].[Description], [m].[FederationId], [m].[IsDeleted], [m].[ModifiedById], [m].[ModifiedOnUtc], [m].[Name] FROM [Team] AS [m] WHERE ([m].[IsDeleted] = 0) AND ([m].[Id] = @__entityId_0) 2019-04-11 16:02:43,945 [12] DEBUG Microsoft.EntityFrameworkCore.Database.Command - A data reader was disposed. 2019-04-11 16:02:43,985 [12] DEBUG Microsoft.EntityFrameworkCore.Database.Connection - Closing connection to database 'Sppd.TeamTuner-TEST' on server '.\SQLEXPRESS'. 2019-04-11 16:02:43,988 [12] DEBUG Microsoft.EntityFrameworkCore.Database.Connection - Closed connection to database 'Sppd.TeamTuner-TEST' on server '.\SQLEXPRESS'. 2019-04-11 16:02:45,054 [12] DEBUG Microsoft.EntityFrameworkCore.Database.Connection - Opening connection to database 'Sppd.TeamTuner-TEST' on server '.\SQLEXPRESS'. 2019-04-11 16:02:45,057 [12] DEBUG Microsoft.EntityFrameworkCore.Database.Connection - Opened connection to database 'Sppd.TeamTuner-TEST' on server '.\SQLEXPRESS'. 2019-04-11 16:02:45,060 [12] DEBUG Microsoft.EntityFrameworkCore.Database.Command - Executing DbCommand [Parameters=[@__entityId_0='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30'] SELECT TOP(2) [m].[Id], [m].[Avatar], [m].[CreatedById], [m].[CreatedOnUtc], [m].[DeletedById], [m].[DeletedOnUtc], [m].[Description], [m].[FederationId], [m].[IsDeleted], [m].[ModifiedById], [m].[ModifiedOnUtc], [m].[Name] FROM [Team] AS [m] WHERE ([m].[IsDeleted] = 0) AND ([m].[Id] = @__entityId_0) 2019-04-11 16:02:45,067 [14] INFO Microsoft.EntityFrameworkCore.Database.Command - Executed DbCommand (7ms) [Parameters=[@__entityId_0='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30'] SELECT TOP(2) [m].[Id], [m].[Avatar], [m].[CreatedById], [m].[CreatedOnUtc], [m].[DeletedById], [m].[DeletedOnUtc], [m].[Description], [m].[FederationId], [m].[IsDeleted], [m].[ModifiedById], [m].[ModifiedOnUtc], [m].[Name] FROM [Team] AS [m] WHERE ([m].[IsDeleted] = 0) AND ([m].[Id] = @__entityId_0) 2019-04-11 16:02:45,092 [14] DEBUG Microsoft.EntityFrameworkCore.Database.Command - A data reader was disposed. 2019-04-11 16:02:45,143 [14] DEBUG Microsoft.EntityFrameworkCore.Database.Connection - Closing connection to database 'Sppd.TeamTuner-TEST' on server '.\SQLEXPRESS'. 2019-04-11 16:02:45,153 [14] DEBUG Microsoft.EntityFrameworkCore.Database.Connection - Closed connection to database 'Sppd.TeamTuner-TEST' on server '.\SQLEXPRESS'.
Les journaux (de débogage) ne contiennent rien d'utile, sauf que je vois que la jointure requis pour charger les propriétés de navigation n'est pas exécuté au niveau SQL:
private static void ConfigureTeam(EntityTypeBuilder<Team> builder) { ConfigureDescriptiveEntity(builder); builder.HasMany(e => e.Users) .WithOne(e => e.Team); // Ignore calculated properties builder.Ignore(e => e.Members) .Ignore(e => e.Leader) .Ignore(e => e.CoLeaders); }
Ce que j'ai essayé:
Ne spécifiez pas de chaîne mais une expression pour spécifier la propriété de navigation à charger:
[Fact] public async Task TestNavigationPropertyLoading() { // Arrange var teamId = Guid.Parse(TestingConstants.Team.HOLY_COW); // Act Team createdTeamWithoutUsers; Team createdTeamWithUsers; using (var scope = ServiceProvider.CreateScope()) { var teamRepository = scope.ServiceProvider.GetService<IRepository<Team>>(); createdTeamWithoutUsers = await teamRepository.GetAsync(teamId); createdTeamWithUsers = await teamRepository.GetAsync(teamId, new[] {nameof(Team.Users)}); } // Assert Assert.Null(createdTeamWithoutUsers.Leader); Assert.False(createdTeamWithoutUsers.Users.Any()); Assert.False(createdTeamWithUsers.CoLeaders.Any()); Assert.NotNull(createdTeamWithUsers.Leader); Assert.True(createdTeamWithUsers.Users.Any()); Assert.True(createdTeamWithUsers.CoLeaders.Any()); }
Configurez également explicitement la relation pour l'entité utilisateur:
namespace Sppd.TeamTuner.Infrastructure.DataAccess.EF.Repositories { internal class Repository<TEntity> : IRepository<TEntity> where TEntity : BaseEntity { /// <summary> /// Gets the entity set. /// </summary> protected DbSet<TEntity> Set => Context.Set<TEntity>(); /// <summary> /// Gets the DB context. /// </summary> protected TeamTunerContext Context { get; } public Repository(TeamTunerContext context) { Context = context; } public async Task<TEntity> GetAsync(Guid entityId, IEnumerable<string> includeProperties = null) { TEntity entity; try { entity = await GetQueryableWithIncludes(includeProperties).SingleAsync(e => e.Id == entityId); } catch (InvalidOperationException) { throw new EntityNotFoundException(typeof(TEntity), entityId.ToString()); } return entity; } protected IQueryable<TEntity> GetQueryableWithIncludes(IEnumerable<string> includeProperties = null) { var queryable = Set; if (includeProperties == null) { return queryable; } foreach (var propertyName in includeProperties) { queryable.Include(propertyName); } return queryable; } } }
Que me manque-t-il?
3 Réponses :
Avez-vous essayé de marquer vos propriétés comme virtuelles? Vous en avez besoin pour activer la navigation à chargement différé selon les documents:
Chargement paresseux
EF Core activera alors le chargement différé pour toute propriété de navigation qui peut être remplacée - c'est-à-dire qu'elle doit être virtuelle et sur une classe dont on peut hériter. Par exemple, dans les entités suivantes, les propriétés de navigation Post.Blog et Blog.Posts seront chargées différemment.
La question concerne EF Core, pas EF6, donc ce n'est pas pertinent.
Non, je ne l'ai pas fait. J'ai essayé de déclarer Team.Users comme virtuel, mais la requête SQL et le résultat restent les mêmes. Je ne veux pas utiliser le chargement différé, mais spécifiez explicitement les propriétés de navigation à charger avec ce que j'essaie d'implémenter ici.
@DavidG Ma réponse est correcte dans les deux versions d'EF. J'ai mis à jour mon lien vers EF core, mais cela n'a pas vraiment d'importance car il ne répond pas à la question opérationnelle de toute façon.
J'ai remarqué quelques problèmes.
Votre approche ne fonctionne que pour le chargement du premier niveau des propriétés de navigation.
foreach (var propertyName in includeProperties) { queryable.Include(propertyName); }
Vous devez utiliser .ThenInclude ()
pour charger les propriétés de navigation imbriquées. Cela rompt votre approche de IEnumerable
en tant que constructeur.
Le deuxième problème concerne votre test lui-même. Il ne vérifie que .Any ()
, mais selon le nom du test, c'est la mauvaise assertion. (Nous ne savons pas si le test échoue car la propriété de navigation n'a jamais été chargée OU elle s'est chargée avec succès, mais il n'y a aucun utilisateur
. Vous devez uniquement vérifier que le la propriété de navigation a été chargée. Quelque chose comme ce qui suit.
DbContext.Entry (createdTeamWithUsers) .Navigation ("Users"). IsLoaded
Vous pouvez charger des propriétés de navigation imbriquées ( docs.microsoft.com/en-us/dotnet/api/... ), votre premier point devrait donc être traité. Pour votre deuxième, je suis d'accord, ce n'est pas encore fini :)
Vous avez raison, vous pouvez charger en utilisant les chaînes Team.Users.Whatever
etc. sécurité.
Vous avez raison, je suis toujours en train de concevoir. Il est toujours possible de remplacer les méthodes de base dans un dépôt spécialisé. Ici, j'aimerais avoir une solution générique.
Ici, vous avez simplement oublié d'utiliser la requête résultante, donc a le même effet que ie aucun effet . Changez simplement le code en Inclure
/ ThenInclude
(et toutes les autres extensions EF Core Queryable
) sont comme les méthodes LINQ Queryable
classiques ( Sélectionnez
, Where
, OrderBy
etc.) qui modifient la source IQueryable
et return strong> le IQueryable
modifié. queryable = queryable.Include(propertyName);
queryable.Where(e => false);
queryable.Include(propertyName);
Doh, maintenant je me sens stupide;) maintenant le test échoue car les deux ont défini les utilisateurs. Je suppose qu'effectuer les get dans différentes portées résoudra ce problème. Merci!
cela ne fonctionnera pas avec le noyau Ef dans le fournisseur de mémoire? Parce que je l'ai fait exactement de la même manière mais mes propriétés de navigation sont nulles
@kuldeep Cela fonctionne pour moi, du moins dans le dernier EFC 3.1.5 officiel sur lequel je teste.
J'ai remarqué que la bibliothèque interne que j'utilise traduit la requête en Inclure ("Adresses") au lieu de Inclure (s => s. Adresses) .. Je soupçonne que cela pourrait être le problème? Est-ce que je manque quelque chose? Comme activer le chargement paresseux ou quelque chose du genre
@kuldeep Le chargement paresseux n'est pas pertinent lorsque vous utilisez le chargement hâtif ( Inclure
). Dans mes tests, la surcharge de chaîne Include
fonctionne également.
@IvanStoev Ce fut un moment de duh pour moi. Je me cogne la tête contre le mur depuis 3 heures et cela a économisé mes efforts et mon temps. Mec tu es une rockstar !! Merci beaucoup.