冠军

导航

GraphQL Part VIII: 使用一对多查询

 

今天,我们引入两个新的实体来处理客户与订单。客户与订单之间是一对多的关系,一个客户可以拥有一个或者多个订单,反过来,一个订单只能被某个客户所拥有。

可以按照 Engity Framework 的约定配置实体之间的关系。 如果某个实体拥有一个第二个实体的集合属性,Entity Framework 会自动创建一对多的关系。该属性被称为导航属性

在 Customer 实体中有一个 Orders 属性,作为集合类型的导航属性。

Customer.cs

public class Customer  
{
    public int CustomerId { get; set; }
    public string Name { get; set; }
    public string BillingAddress { get; set; }
    public IEnumerable<Order> Orders { get; set; }
}

 

大多情况下,定义一个导航属性就已经足够了,但是,还是建议定义完全的关系。在第二个实体上定义一个引用导航属性的外键属性。

Order.cs

public class Order  
{
    public int OrderId { get; set; }
    public string Tag { get; set;}
    public DateTime CreatedAt { get; set;}

    public Customer Customer { get; set; }
    public int CustomerId { get; set; }
}

 

一旦定义了所有需要的关系,就可以使用 dotnet CLI 创建一个 migration 然后更新数据库。

dotnet ef migrations add OneToManyRelationship  
dotnet ef database update 

 

我们还需要创建两个新的 ObjectGraphType 。

OrderType.cs

public class OrderType: ObjectGraphType <Order> {  
    public OrderType(IDataStore dataStore) {
        Field(o => o.Tag);
        Field(o => o.CreatedAt);
        Field <CustomerType, Customer> ()
            .Name("Customer")
            .ResolveAsync(ctx => {
                return dataStore.GetCustomerByIdAsync(ctx.Source.CustomerId);
            });
    }
}

 

以及 CustomerType.cs

public class CustomerType: ObjectGraphType <Customer> {  
    public CustomerType(IDataStore dataStore) {
        Field(c => c.Name);
        Field(c => c.BillingAddress);
        Field <ListGraphType<OrderType> , IEnumerable <Order>> ()
            .Name("Orders")
            .ResolveAsync(ctx => {
                return dataStore.GetOrdersByCustomerIdAsync(ctx.Source.CustomerId);
            });
    }
}

 

为了暴露两个新的端点,还需要在 InventoryQuery 中注册这两个类型。

InventoryQuery.cs

Field<ListGraphType<OrderType>, IEnumerable<Order>>()  
    .Name("Orders")
    .ResolveAsync(ctx =>
    {
        return dataStore.GetOrdersAsync();
    });

Field<ListGraphType<CustomerType>, IEnumerable<Customer>>()  
    .Name("Customers")
    .ResolveAsync(ctx =>
    {
        return dataStore.GetCustomersAsync();
    });

 

在后台的数据仓库中,还需要提供相应的数据访问方法。

DataStore.cs

public async Task <IEnumerable<Order>> GetOrdersAsync() {  
    return await _applicationDbContext.Orders.AsNoTracking().ToListAsync();
}

public async Task <IEnumerable<Customer>> GetCustomersAsync() {  
    return await _applicationDbContext.Customers.AsNoTracking().ToListAsync();
}

public async Task <Customer> GetCustomerByIdAsync(int customerId) {  
    return await _applicationDbContext.Customers.FindAsync(customerId);
}

public async Task <IEnumerable<Order>> GetOrdersByCustomerIdAsync(int customerId) {  
    return await _applicationDbContext.Orders.Where(o => o.CustomerId == customerId).ToListAsync();
}

 

同时,我们还增加两个方法用于创建 Customer 和 Order,

public async Task<Order> AddOrderAsync(Order order)  
{
    var addedOrder = await _applicationDbContext.Orders.AddAsync(order);
    await _applicationDbContext.SaveChangesAsync();
    return addedOrder.Entity;
}

public async Task<Customer> AddCustomerAsync(Customer customer)  
{         
    var addedCustomer = await _applicationDbContext.Customers.AddAsync(customer);
    await _applicationDbContext.SaveChangesAsync();
    return addedCustomer.Entity;
}

 

在上一篇 Blog 中,我们创建过 InputObjectGraphType 用于 Item 的创建,与其类似,我们也需要为 Customer 和 Order 创建对应的 InputObjectGraph。

OrderInputType.cs

public class OrderInputType : InputObjectGraphType {  
    public OrderInputType()
    {
        Name = "OrderInput";
        Field<NonNullGraphType<StringGraphType>>("tag");
        Field<NonNullGraphType<DateGraphType>>("createdAt");
        Field<NonNullGraphType<IntGraphType>>("customerId");
    }
}

 

CustomerInputType.cs

public class CustomerInputType : InputObjectGraphType {  
    public CustomerInputType()
    {
        Name = "CustomerInput";
        Field<NonNullGraphType<StringGraphType>>("name");
        Field<NonNullGraphType<StringGraphType>>("billingAddress");
    }
}

 

最后,我们需要注册所有新的的类型到 DI 系统中。在 Startup 的 ConfigureServices 方法中如下注册。

public void ConfigureServices(IServiceCollection services)  
{ 
....
....
    services.AddScoped<CustomerType>();
    services.AddScoped<CustomerInput>();
    services.AddScoped<OrderType>();
    services.AddScoped<OrderInputType>();
}

 

现在,如果您运行应用,将会看到如下的错误信息:

"No parameterless constructor defined for this object."

通过调查 graphql-dotnet,我发现了这个问题:issue

对于当前的方案, Schema 的构造函数注入不能工作。DI 系统可以获取类型一次,但是不能对该对象链的子级再次获取。简单来说,如果在 InventoryQuery 中通过构造函数注入了 IDataSource 一次 ,但是,你不能在其他的 Graph Type 构造函数中注入;例如 CustomerType。但是,这不是我们期望的行为,因此,使用 IDependencyResolver,在 DI 系统中注册 IDependencyResolver,并确保提供一个有限的生命周期。

services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));  

 

需要在 InventorySchemacs 中做一点修改,修改代码,在构造函数中注入 IDependencyResolver。

InventorySchema.cs

public class InventorySchema: Schema {  
    public InventorySchema(IDependencyResolver resolver): base(resolver) {
        Query = resolver.Resolve < InventoryQuery > ();
        Mutation = resolver.Resolve < InventoryMutation > ();
    }
}

 

现在,重新运行应用,并验证你可以访问新增加的字段。

 

posted on 2020-03-28 18:02  冠军  阅读(271)  评论(0编辑  收藏