In a real-world scenario, only a limited quantity of a particular item belongs to a particular order. Imagine, you have an order cart containing references to some selected items with their respective ordered quantities. The end result is you got a relationship something like: an order can have many items whereas an item can be part of multiple orders.

By EF Core conventions, in a many-to-many relation, you have two standalone entities and a third entity between them which represents a relationship bridge. In our case, the bridging entity will be the OrderItem

public class OrderItem  
{
    public int Id { get; set; }

    public int ItemId { get; set; }
    public Item Item { get; set; }

    public int Quantity { get; set; }      

    public int OrderId { get; set; }
    public Order Order { get; set; }
}
OrderItem.cs

Here we have two reference navigation properties for each side of the relation i.e. Order and Item. Also, we have two foreign key properties i.e. ItemId and OrderId.

For a fully defined relationship, we also have individual collection navigation property of OrderItem on each side of the standalone entities,

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; }
    public IEnumerable<OrderItem> OrderItems { get; set; }
}
Order.cs
public class Item
{
    public int Id { get; set; }
    public string Tag { get; set; }
    public string Title { get; set; }
    public decimal Price { get; set; }
    public IEnumerable<OrderItem> OrderItems { get; set; }
}
Item.cs

We are using an In-Memory database so no migration script is needed for each iterative changes on the database. But, once you have configured all the necessary relationships, create a migration and update your development/production database using dotnet CLI with the following commands,

dotnet ef migrations add ManyToManyRelationship  
dotnet ef database update  

We can add a GraphQL end-point for adding an item to a particular order. To do that we need an InputGraphTypeType for OrderItem.

public class OrderItemInputType : InputObjectGraphType  
{
    public OrderItemInputType()
    {
        Name = "OrderItemInput";
        Field<NonNullGraphType<IntGraphType>>("quantity");
        Field<NonNullGraphType<IntGraphType>>("itemId");
        Field<NonNullGraphType<IntGraphType>>("orderId");
    }
}
OrderItemInputType.cs

As for the end-point, we registered a new mutation field inside GameStoreMutation.cs. The field is simply named addOrderItem,

FieldAsync<OrderItemType>(
    "addOrderItem", 
    arguments: new QueryArguments(
        new QueryArgument<NonNullGraphType<OrderItemInputType>> { Name = "orderItem" }
    ),
    resolve: async ctx =>
    {
        var orderItem = ctx.GetArgument<OrderItem>("orderItem");
        return await repository.AddOrderItem(orderItem);
    });
GameStoreMutation.cs

Newly added OrderItemType is as following,

public class OrderItemType : ObjectGraphType<OrderItem>
{
    public OrderItemType(IRepository repository)
    {
        Field(i => i.ItemId);

        FieldAsync<ItemType, Item>("item", resolve: ctx =>
        {
            return repository.GetItemById(ctx.Source.ItemId);
        });

        Field(i => i.Quantity);

        Field(i => i.OrderId);

        FieldAsync<OrderType, Order>("order", resolve: ctx =>
        {
            return repository.GetOrderById(ctx.Source.OrderId);
        });
    }
}
OrderItemType.cs

Newly registered methods from Repository.cs are as following,

public async Task<Item> GetItemById(int itemId)
{
    return await _applicationDbContext.Items.FindAsync(itemId);
}

public async Task<Order> GetOrderById(int orderId)
{
    return await _applicationDbContext.Orders.FindAsync(orderId);
}

public async Task<OrderItem> AddOrderItem(OrderItem orderItem)
{
    var addedOrderItem = await _applicationDbContext.OrderItem.AddAsync(orderItem);
    await _applicationDbContext.SaveChangesAsync();
    return addedOrderItem.Entity;
}
Repository.cs

I've also threw in an additional field for querying a list of all the OrderItem at once,

FieldAsync<ListGraphType<OrderItemType>, IReadOnlyCollection<OrderItem>>(
    "orderItem",
    resolve: ctx =>
    {
        return repository.GetOrderItem();
    });
GameStoreQuery.cs

Repository code for GetOrderItem is as following,

public async Task<IReadOnlyCollection<OrderItem>> GetOrderItem()
{
    return await _applicationDbContext.OrderItem.AsNoTracking().ToListAsync();
}
Repository.cs

Last but not least, don't forget to register the newly added graph types with the DI system. Services registration inside ConfigureServices are as followings,

services.AddTransient<OrderItemType>();
services.AddTransient<OrderItemInputType>();
Startup.cs

That's all about it. Run the application and try to add an item to a particular order with a mutation like the following illustration,

Part-IX

fiyazbinhasan/GraphQLCoreFromScratch
https://fiyazhasan.me/tag/graphql/. Contribute to fiyazbinhasan/GraphQLCoreFromScratch development by creating an account on GitHub.

Configuring Many To Many Relationships in Entity Framework Core