• 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 > ();
        }
    }

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

  • 相关阅读:
    【bzoj4066】 简单题
    【bzoj1941】 Sdoi2010—Hide and Seek
    【bzoj2648】 SJY摆棋子
    【poj2154】 Color
    【poj2409】 Let it Bead
    【codevs1106】 篝火晚会
    【poj3270】 Cow Sorting
    【bzoj1004】 HNOI2008—Cards
    【bzoj3143】 Hnoi2013—游走
    【codeforces 749E】 Inversions After Shuffle
  • 原文地址:https://www.cnblogs.com/haogj/p/9177106.html
Copyright © 2020-2023  润新知