.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);
}
}
因此,如果我理解正确的话,投影用于消除使用普通 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);
}
}