هنحتاج نعمل Implement للـ Method اللي من خلالها هنعمل Create للـ Order

أول حاجة اني أمسك الـ Business Document الخاص بيها وأبدأ اني أرتبه في دماغي خطوة خطوة لأن الـ Business ممكن يكون فيه 5 سيناريو وانت متعملش غير واحد بس نكتب Pseudocode لكل خطوة:

1. Get Basket From Baskets Repo

هخلي الـ CLR في الـ Ctor يعملي Inject لـ Object من Class اللي اسمه BasketRepository

public class OrderService : IOrderService
{
	private readonly IBasketRepository _basketRepo;
	public OrderService (
		IBasketRepository basketRepo
	)
	{
		_basketRepo = basketRepo;
	}
 
	public async Task<Order> CreateOrderAsync(string buyerEmail, string basketId, int deliveryMethod)
	{
		var basket = await _basketRepo.GetBasketAsync(basketId);
	}
}

2. Get Selected Items at Basket From Products Repo

public class OrderService : IOrderService
{
	private readonly IBasketRepository _basketRepo;
	private readonly IGenericRepository<Product> _productsRepo;
	
	public OrderService (
		IBasketRepository basketRepo,
		IGenericRepository<Product> productsRepo
	)
	{
		_basketRepo = basketRepo;
		_productsRepo = productsRepo;
	}
 
	public async Task<Order> CreateOrderAsync(string buyerEmail, string basketId, int deliveryMethod)
	{
		// 1
		var basket = await _basketRepo.GetBasketAsync(basketId);
		// 2
		var orderItems = new List<OrderItem>();
		if(basket?.Items?.Count > 0)
		{
			foreach (var item in basket.Items)
			{
				// I don't want to Include
				var product = await _productsRepo.GetAsync(item.Id);
				var productItemOrdered = new ProductItemOrdered(item.Id, product.Name, product.PictureUrl);
				var orderItem = new OrderItem(productItemOrdered, product.Price, item.Quantity);
				orderItems.Add(orderItem);
			}
		}
		// 3
	}
}

خد بالك ان فيه معلومات زي الـ Name والـ Price ممكن متبقاش صح في الـ Basket أو يتلعب فيها فعشان كدا بناخد البيانات دي من الـ Product نفسه

3. Calculate Subtotal

// 3
var subtotal = orderItems.Sum(orderItem => orderItem.Price * orderItem.Quantity);

اتكلمنا قبل كدا عن الـ Cs Aggregate، بس فيه معلومة زيادة هنا عندنا العمليات اللي هي Sum و Average وكدا (عددهم خمسة Aggregate Operators) كل واحدة بتعمل حاجة معينة ومش بتعمل حاجة غيرها انما عندنا واحدة سادسة اللي هي Aggregate وبتخليك انت تحدد انت عايز تعمل ايه وتحدد الـ Accumulator value فهي بتديك الحرية بس بتبقا أصعب

4. Get delivery method from Delivery Methods Repo

public class OrderService : IOrderService
{
	private readonly IBasketRepository _basketRepo;
	private readonly IGenericRepository<Product> _productsRepo;
	private readonly IGenericRepository<DeliveryMethod> _deliveryMethodsRepo;
	
	
	public OrderService (
		IBasketRepository basketRepo,
		IGenericRepository<Product> productsRepo,
		IGenericRepository<DeliveryMethod> deliveryMethodsRepo
	)
	{
		_basketRepo = basketRepo;
		_productsRepo = productsRepo;
		_deliveryMethodsRepo = deliveryMethodsRepo;
	}
}
// 4
var deliveryMethod = await _deliveryMethodsRepo.GetAsync(deliveryMethodId);

5. Create Order

public class OrderService : IOrderService
{
	private readonly IBasketRepository _basketRepo;
	private readonly IGenericRepository<Product> _productsRepo;
	private readonly IGenericRepository<DeliveryMethod> _deliveryMethodsRepo;
	private readonly IGenericRepository<Order> _ordersRepo;
	
	
	public OrderService (
		IBasketRepository basketRepo,
		IGenericRepository<Product> productsRepo,
		IGenericRepository<DeliveryMethod> deliveryMethodsRepo,
		IGenericRepository<Order> ordersRepo
	)
	{
		_basketRepo = basketRepo;
		_productsRepo = productsRepo;
		_deliveryMethodsRepo = deliveryMethodsRepo;
		_ordersRepo = ordersRepo;
	}
}
// 5
var order = new Order(buyerEmail, shippingAddress, deliveryMethod, orderItems, subtotal);
await _ordersRepo.AddAsync(order);

المشكلة ان في الـ Generic Repository مكنش عندي Add ولا Update ولا Delete عشان كدا لازم اضيفهم Interface:

public interface IGenericRepository<T> where T : BaseEntity
{
    Task<T?> GetAsync(int id);
    Task<IReadOnlyList<T>> GetAllAsync();
 
    Task<T?> GetWithSpecAsync(ISpecification<T> spec);
    Task<IReadOnlyList<T>> GetAllWithSpecAsync(ISpecification<T> spec);
    Task<int> GetCountWithSpecAsync(ISpecification<T> spec);
 
	Task AddAsync(T entity);
	void Update(T entity);
	void Delete(T entity);
}

Class:

public async Task AddAsync(T entity)
	=> await _dbContext.AddAsync(entity); // Syntax Suger
	// _dbContext.Set<T>().AddAsync(entity);
 
public void Update(T entity)
	=> _dbContext.Update(entity);
 
public void Delete(T entity)
	=> _dbContext.Remove(entity);

6. Save to Database

هل ليا Access على الـ DbContext؟ لا مليش بتعامل مع ال Generic Repository وهي اللي بتتعامل مع الـ DbContext عشان كدا مليش Access على SaveChanges

فالحل هيبقا في الـ Unit of work: الكلاس اللي بيتعتبر unit للـ Work بتاعنا مع الـ DbContext روح شوف دي الأول Unit of work for Project

فاحنا هنعمل كومنت لكل الـ Repositories اللي عندي وأطلب من الـ CLR انه يعمل Inject لـ Object من الـ UnitOfWork

public class OrderService : IOrderService
{
	private readonly IBasketRepository _basketRepo;
	private readonly IUnitOfWork _unitOfWork;
	
	// private readonly IGenericRepository<Product> _productsRepo;
	// private readonly IGenericRepository<DeliveryMethod> _deliveryMethodsRepo;
	// private readonly IGenericRepository<Order> _ordersRepo;
	
	
	public OrderService (
		IBasketRepository basketRepo,
		IUnitOfWork unitOfWork
		// IGenericRepository<Product> productsRepo,
		// IGenericRepository<DeliveryMethod> deliveryMethodsRepo,
		// IGenericRepository<Order> ordersRepo
	)
	{
		_basketRepo = basketRepo;
		_unitOfWork = unitOfWork
		// _productsRepo = productsRepo;
		// _deliveryMethodsRepo = deliveryMethodsRepo;
		// _ordersRepo = ordersRepo;
	}
}

محتاج أعمل Allow للـ DI للـ Unit of work فنروح للـ Main ونشوف فايل الـ AddApplicationServices

services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));

ودلوقتي مش محتاج أعمل Allow DI للـ Generic Repository خلاص لأني أصلا في الـ Unit of work بعملها بايدي فمحتاجين نعملها كومنت او نشيلها

وبرضو لما بنيجي نعمل Object من الـ UnitOfWork بنطلب من الـ CLR اوبجكت من الـ DbContext ودا بنبعته للـ Generic Repository فاحنا مش بنطلب من الـ CLR انما هو بيجيلنا جاهز لانه اتطلب في الـ Ctor بتاع الـ Unit of work

ولو تاخد بالك ان الـ UnitOfWork ملوش علاقة بالـ BasketRepository لأنه بيكلم Database مختلفة والمفروض انه يتعمله UnitOfWork لوحده بس معملتوش عشان مفيهوش غير Repository واحدة بس

المفروض دلوقتي نغير الكود اللي فوق كله ونستخدم الـ unitOfWork اللي جواها ميثود الـ Repository وخد بالك ان الـ Implementation بتاعها هو هو الـ Scoped Life Time (انها لما تطلب مرة تانية هيلاقيها موجودة)

Final Code

public class OrderService : IOrderService
{
	private readonly IBasketRepository _basketRepo;
	private readonly IUnitOfWork _unitOfWork;
	public OrderService (
		IBasketRepository basketRepo,
		IUnitOfWork unitOfWork
	)
	{
		_basketRepo = basketRepo;
		_unitOfWork = unitOfWork;
	}
 
	public async Task<Order?> CreateOrderAsync(string buyerEmail, string basketId, int deliveryMethod)
	{
		// 1
		var basket = await _basketRepo.GetBasketAsync(basketId);
		// 2
		var orderItems = new List<OrderItem>();
		if(basket?.Items?.Count > 0)
		{
			var productRepository = _unitOfWork.Repository<Product>();
			foreach (var item in basket.Items)
			{
				var product = await productRepository.GetAsync(item.Id);
				var productItemOrdered = new ProductItemOrdered(item.Id, product.Name, product.PictureUrl);
				var orderItem = new OrderItem(productItemOrdered, product.Price, item.Quantity);
				orderItems.Add(orderItem);
			}
		}
		// 3
		var subtotal = orderItems.Sum(orderItem => orderItem.Price * orderItem.Quantity);
		// 4
		var deliveryMethod = await _deliveryMethodsRepo.GetAsync(deliveryMethodId);
		// 5
		var order = new Order(buyerEmail, shippingAddress, deliveryMethod, orderItems, subtotal);
await _ordersRepo.Add(order);
		// 6
		var result = await _unitOfWork.CompleteAsync();
		if(result <= 0) return null;
		return order;
	}
}

الـ result دي هي عدد الـ Row affected من الآخر