.NET GraphQL 热巧克力投影仅适用于请求的实体列表,但不适用于请求的单个实体

.NET GraphQL Hot Chocolate Projections only working for requested List of Entities but not for requested Single Entities

因此,如果我理解正确的话,投影用于消除使用普通 REST-API 时会遇到的过度/不足获取问题。

我已经为我的 Author-ObjectType 实现了 GetAll 功能,并且它工作得很好。我的问题是我不会让它为我的 GetById 功能工作。

我不工作的意思是,总是完整的 SQL 语句被触发到数据库中,而不仅仅是选择了请求的字段。

也许我理解错了,投影只能用于实体列表?或者在这种情况下 IQueryables.

如果这仅适用于 Lists/IQueryables,那么为过滤后的实体实现它的方法是什么(比如如果我想要按 ID 或姓名等创建作者)

CallHierarchy:AuthorQuery -> AuthorService -> Repository

作者查询:

[ExtendObjectType(typeof(Query))]
public class AuthorQuery {
    [UseProjection]
    public async Task<IQueryable<Author>> Authors([Service] IAuthorService authorService) {
        return await authorService.GetAsync();
    }

    [UseProjection]
    public async Task<Author> AuthorById([Service] IAuthorService authorService, int id) {
        var result = await authorService.GetAsync(author => author.Id == id);
        return result.Single();
    }
}

AuthorService(此时只是基础服务导致 AuthorService 调用父方法):

public class BaseService<TEntity> : IBaseService<TEntity> where TEntity : BaseEntity {
    protected readonly IRepository<TEntity> repository;

    public BaseService(IRepository<TEntity> repository) {
        this.repository = repository;
    }
    public virtual async Task<IQueryable<TEntity>> GetAsync(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] includes) {
        return await repository.GetAsync(filter, includes);
    }

    public virtual async Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] includes) {
        return await repository.GetFirstAsync(filter, includes);
    }
}
public class Repository<TEntity> : IRepository<TEntity> where TEntity : BaseEntity {
    protected readonly LibraryContext context;
    public Repository(IDbContextFactory<LibraryContext> contextFactory) {
        this.context = contextFactory.CreateDbContext();
    }

    public async Task<IQueryable<TEntity>> GetAsync(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] includes) {
        IQueryable<TEntity> query = context.Set<TEntity>();

        foreach (var include in includes) {
            query = query.Include(include);
        }

        if (filter != null) {
            query = query.Where(filter);
        }

        return query.AsQueryable();
    }

    public async Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] includes) {
        IQueryable<TEntity> query = context.Set<TEntity>();

        foreach (var include in includes) {
            query = query.Include(include);
        }

        if (filter != null) {
            query = query.Where(filter);
        }

        return await query.AsQueryable().FirstOrDefaultAsync();
    }
}

作者类型定义:

public class AuthorType: ObjectType<Author> { }

Program.cs --> 只有服务定义和 GraphQL 特定的东西

builder.Services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddTransient(typeof(IBookRepository), typeof(BookRepository));
builder.Services.AddTransient(typeof(IAuthorRepository), typeof(AuthorRepository));
builder.Services.AddTransient(typeof(IBaseService<>), typeof(BaseService<>));
builder.Services.AddTransient<IBookService, BookService>();
builder.Services.AddTransient<IAuthorService, AuthorService>();

builder.Services
        .AddGraphQLServer()
        .AddProjections()
        .AddQueryType<Query>()
        .AddTypeExtension<BookQuery>()
        .AddTypeExtension<AuthorQuery>()
        .AddMutationType<Mutation>()
        .AddTypeExtension<BookMutation>()
        .AddTypeExtension<AuthorMutation>()
        .AddType<BookType>()
        .AddType<AuthorType>()
        .AddType<BookCreate>()
        .AddType<BookUpdate>()
        .AddType<AuthorCreate>()
        .AddType<AuthorUpdate>();

这是为以下请求字段生成的 sql 语句:

{
  id
  firstName        
  books {
    id
    title
  }
}

更新:(生成 sql 个查询)

作者编号:

姓氏在查询中

SELECT [a].[Id], [a].[FirstName], [a].[LastName], [t].[AuthorsId], [t].[BooksId], [t].[Id], [t].[Title]
FROM [Authors] AS [a]
LEFT JOIN (
    SELECT [a0].[AuthorsId], [a0].[BooksId], [b].[Id], [b].[Title]
    FROM [AuthorBook] AS [a0]
    INNER JOIN [Books] AS [b] ON [a0].[BooksId] = [b].[Id]
) AS [t] ON [a].[Id] = [t].[AuthorsId]
WHERE [a].[Id] = @__id_0
ORDER BY [a].[Id], [t].[AuthorsId], [t].[BooksId]

姓氏已被忽略 获取全部:

SELECT [a].[Id], [a].[FirstName], [t].[Id], [t].[Title], [t].[AuthorsId], [t].[BooksId]
FROM [Authors] AS [a]
LEFT JOIN (
    SELECT [b].[Id], [b].[Title], [a0].[AuthorsId], [a0].[BooksId]
    FROM [AuthorBook] AS [a0]
    INNER JOIN [Books] AS [b] ON [a0].[BooksId] = [b].[Id]
) AS [t] ON [a].[Id] = [t].[AuthorsId]
ORDER BY [a].[Id], [t].[AuthorsId], [t].[BooksId]

更新 (2) 来自 BaseService、AuthorService 和 AuthorRepository 的代码

public class BaseService<TEntity> : IBaseService<TEntity> where TEntity : BaseEntity {
        protected readonly IRepository<TEntity> repository;

        public BaseService(IRepository<TEntity> repository) {
            this.repository = repository;
        }
        public virtual async Task<IQueryable<TEntity>> GetAsync(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] includes) {
            return await repository.GetAsync(filter, includes);
        }

        public virtual async Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] includes) {
            return await repository.GetFirstAsync(filter, includes);
        }

        public virtual Task<TEntity> AddAsync(TEntity entity) {
            return repository.AddAsync(entity);
        }
        public virtual async Task<TEntity> UpdateAsync(TEntity entity) {
            return await repository.UpdateAsync(entity);
        }

        public virtual async Task<bool> ExistsAsync(int id) {
            return await repository.ExistsAsync(id);
        }

        public virtual async Task RemoveAsync(TEntity entity) {
            await repository.RemoveAsync(entity);
        }

    }
public class AuthorService : BaseService<Author>, IAuthorService {
    public AuthorService(IAuthorRepository repository) : base(repository) {
    }
}

作者资料库:

public class AuthorRepository : Repository<Author>, IAuthorRepository {
    public AuthorRepository(IDbContextFactory<LibraryContext> contextFactory) : base(contextFactory) { }

    public override async Task<Author> AddAsync(Author author) {
        author.Books = await context.Books.Where(book => author.Books.Select(x => x.Id).ToList().Contains(book.Id)).ToListAsync();
        return await base.AddAsync(author);
    }

    public override async Task<Author> UpdateAsync(Author author) {
        var authorToUpdate = await GetFirstAsync(a => a.Id == author.Id, a => a.Books);
        if (authorToUpdate == null) {
            throw new ArgumentNullException(nameof(authorToUpdate));
        }

        authorToUpdate.FirstName = author.FirstName;
        authorToUpdate.LastName = author.LastName;

        if (author.Books.Count != authorToUpdate.Books.Count || !authorToUpdate.Books.All(author.Books.Contains)) {
            authorToUpdate.Books.UpdateManyToMany(author.Books, b => b.Id);
            authorToUpdate.Books = await context.Books.Where(book => author.Books.Select(a => a.Id).ToList().Contains(book.Id)).ToListAsync();
        }

        return await base.UpdateAsync(authorToUpdate);
    }
}

请注意,我按照建议更新了 AuthorQuery 的 AuthorById 函数,如下所示

[UseProjection]
[UseSingleOrDefault]
public async Task<IQueryable<Author>> AuthorById([Service] IAuthorService authorService, int id) {
    return await authorService.GetAsync(author => author.Id == id, author => author.Books);
}

你试过了吗?

[ExtendObjectType(typeof(Query))]
public class AuthorQuery {
    [UseProjection]
    public async Task<IQueryable<Author>> Authors([Service] IAuthorService authorService) {
        return await authorService.GetAsync();
    }

    [UseSingleOrDefault]
    [UseProjection]
    public async Task<IQueryable<Author>> AuthorById([Service] IAuthorService authorService, int id) {
        return await authorService.GetAsync(author => author.Id == id);
    }
}