3
votes

La propriété de navigation principale EF ne se charge pas

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?


0 commentaires

3 Réponses :


0
votes

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.

Source: chargement des données associées


3 commentaires

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.




6
votes

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é.

Ici, vous avez simplement oublié d'utiliser la requête résultante, donc

queryable = queryable.Include(propertyName);

a le même effet que

queryable.Where(e => false);

ie aucun effet .

Changez simplement le code en

queryable.Include(propertyName);


6 commentaires

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.