冠军

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

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   冠军  阅读(569)  评论(0编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2012-03-28 window.parent ,window.top,window.self 详解
点击右上角即可分享
微信分享提示