Insert nested entities like one-to-many into database using Entity Framework Core

Viewed 2021

I am using EF Core with a generic repository pattern.

The Order table has a one-to-many relationship with OrderItem. OrderId is a foreign key in OrderItem.

The POCO model classes:

    public class OrderEntity
    {
        public long Id { get; set; }

        public long PaymentReference { get; set; }
        public DateTimeOffset ModifiedDate { get; set; }
        public DateTimeOffset CreatedDate { get; set; }

        public IList<OrderItemEntity> OrderItemEntities { get; set; }
    }

    public class OrderItemEntity
    {
        public long Id { get; set; }
        public long OrderId { get; set; }
        public Position Position { get; set; }
        public AlterationType Type { get; set; }
        public AlterationItemType ItemType { get; set; }
        public AlterationStatus Status { get; set; }
        public double Measurment { get; set; }
        public double AlterationCost { get; set; }
    }

My service:

    public class OrderService : Repository<OrderEntity>, IOrderService
    {
        public OrderService(SalesContext context) : base(context)
        {
        }

        public long CreateOrder(OrderEntity orderEntity)
        {
            base.Add(orderEntity);
            base.Save();

            return orderEntity.Id;
        }

        public IEnumerable<OrderEntity> FilterOrderByFilterCriteria(long Id)
        {
            return base.SelectByCriteria(item => item.Id == Id);
        }

        public IEnumerable<OrderEntity> GetAllOrders()
        {
            return base.SelectAll("OrderItem");
        }
}

Generic repository:

public class Repository<T> : IRepository<T> where T : class
{
        private readonly SalesContext _dbContext;

        public Repository(SalesContext dbContext)
        {
            _dbContext = dbContext;
        }

        public virtual T Add(T obj)
        {
            _dbContext.Set<T>().Add(obj);
            return obj;
        }

        public virtual void Update(T entity)
        {
            _dbContext.Entry(entity).State = EntityState.Modified;
            _dbContext.SaveChanges();
        }

        public virtual T Delete(T obj)
        {
            _dbContext.Set<T>().Remove(obj);
            return obj;
        }

        public virtual IEnumerable<T> SelectAll(string entityToInclude)
        {
            return _dbContext.Set<T>().Include(entityToInclude).ToList();
        }

        public virtual IEnumerable<T> SelectByCriteria(Expression<Func<T, bool>> predicate)
        {
            return _dbContext.Set<T>().Where(predicate);
        }

        public virtual string Save()
        {
            _dbContext.SaveChanges();
            return string.Empty;
        }
    }
}

How can I insert multiple order-item when I am inserting order in the same connection to the database?

3 Answers

Since OrderEntity has a one-to-many relationship with OrderItemEntity and OrderEntity has a navigation property called OrderItemEntities of type IList<OrderItemEntity>, initializing this property to a new list and then adding all the child OrderItemEntity objects to this list before adding this OrderEntity to the database, it will add all the OrderItemEntity objects in that list to the database along with OrderEntity

public IActionResult Create(OrderWriteModel model)
{
    
    var orderItems = new List<OrderItemEntity>();
    foreach(var item in model.OrderItems)
    {
          OrderItemEntity orderItem = YourMappingToOrderItem(item);
          orderItems.Add(orderItem);
    }

    OrderEntity order = YourMappingToOrder(model);

    //Assign the list to the parent's property
    order.OrderItemEntities = orderItems;

    //This will not only create an order but also all the OrderItemEntity objects
    _orderService.CreateOrder(order);

     //...
}

You just need to assign to OrderEntity.OrderItemEntities. That's all the code you need.

You don't even need to assign to the foreign key. EF Core will automatically populate the required fields.

For convenience, you can also include the OrderEntity navigation property in OrderItemEntity:

public class OrderItemEntity
{
    //...
    public OrderEntity OrderEntity { get; set; }
}

This is optional.

When you fetch an order from the database, for instance, using the order Id:

context.Orders.FirstOrDefault(o => o.Id == orderId)

This will not populate the OrderItemEntities property, this will be null as related entities are not included by default. If you want to include all the OrderItemEntitys, chain a call to Include():

context.Orders
    .Include(o => o.OrderItemEntities)
    .FirstOrDefault(o => o.Id == orderId)

This will include all the children.

If you call CreateOrder as below items will be inserted alongside order;

var newOrderId = CreateOrder(new OrderEntity (){
...
OrderItemEntities  = new List<OrderItemEntity> (){new OrderItemEntity{...}},
...
});

Side note: (independent of EF, but certainly appies).. Collection Properties (e.g. IList) should NEVER be null, they should always be initialized at object creation.

Related