using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Ordering.Infrastructure; using System; using System.Threading; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure { public class OrderingContext : DbContext,IUnitOfWork { const string DEFAULT_SCHEMA = "ordering"; public DbSet<Order> Orders { get; set; } public DbSet<OrderItem> OrderItems { get; set; } public DbSet<PaymentMethod> Payments { get; set; } public DbSet<Buyer> Buyers { get; set; } public DbSet<CardType> CardTypes { get; set; } public DbSet<OrderStatus> OrderStatus { get; set; } private readonly IMediator _mediator; public OrderingContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<ClientRequest>(ConfigureRequests); modelBuilder.Entity<Address>(ConfigureAddress); modelBuilder.Entity<PaymentMethod>(ConfigurePayment); modelBuilder.Entity<Order>(ConfigureOrder); modelBuilder.Entity<OrderItem>(ConfigureOrderItems); modelBuilder.Entity<CardType>(ConfigureCardTypes); modelBuilder.Entity<OrderStatus>(ConfigureOrderStatus); modelBuilder.Entity<Buyer>(ConfigureBuyer); } private void ConfigureRequests(EntityTypeBuilder<ClientRequest> requestConfiguration) { requestConfiguration.ToTable("requests", DEFAULT_SCHEMA); requestConfiguration.HasKey(cr => cr.Id); requestConfiguration.Property(cr => cr.Name).IsRequired(); requestConfiguration.Property(cr => cr.Time).IsRequired(); } void ConfigureAddress(EntityTypeBuilder<Address> addressConfiguration) { addressConfiguration.ToTable("address", DEFAULT_SCHEMA); addressConfiguration.Property<int>("Id") .IsRequired(); addressConfiguration.HasKey("Id"); } void ConfigureBuyer(EntityTypeBuilder<Buyer> buyerConfiguration) { buyerConfiguration.ToTable("buyers", DEFAULT_SCHEMA); buyerConfiguration.HasKey(b => b.Id); buyerConfiguration.Ignore(b => b.DomainEvents); buyerConfiguration.Property(b => b.Id) .ForSqlServerUseSequenceHiLo("buyerseq", DEFAULT_SCHEMA); buyerConfiguration.Property(b=>b.IdentityGuid) .HasMaxLength(200) .IsRequired(); buyerConfiguration.HasIndex("IdentityGuid") .IsUnique(true); buyerConfiguration.HasMany(b => b.PaymentMethods) .WithOne() .HasForeignKey("BuyerId") .OnDelete(DeleteBehavior.Cascade); var navigation = buyerConfiguration.Metadata.FindNavigation(nameof(Buyer.PaymentMethods)); navigation.SetPropertyAccessMode(PropertyAccessMode.Field); } void ConfigurePayment(EntityTypeBuilder<PaymentMethod> paymentConfiguration) { paymentConfiguration.ToTable("paymentmethods", DEFAULT_SCHEMA); paymentConfiguration.HasKey(b => b.Id); paymentConfiguration.Ignore(b => b.DomainEvents); paymentConfiguration.Property(b => b.Id) .ForSqlServerUseSequenceHiLo("paymentseq", DEFAULT_SCHEMA); paymentConfiguration.Property<int>("BuyerId") .IsRequired(); paymentConfiguration.Property<string>("CardHolderName") .HasMaxLength(200) .IsRequired(); paymentConfiguration.Property<string>("Alias") .HasMaxLength(200) .IsRequired(); paymentConfiguration.Property<string>("CardNumber") .HasMaxLength(25) .IsRequired(); paymentConfiguration.Property<DateTime>("Expiration") .IsRequired(); paymentConfiguration.Property<int>("CardTypeId") .IsRequired(); paymentConfiguration.HasOne(p => p.CardType) .WithMany() .HasForeignKey("CardTypeId"); } void ConfigureOrder(EntityTypeBuilder<Order> orderConfiguration) { orderConfiguration.ToTable("orders", DEFAULT_SCHEMA); orderConfiguration.HasKey(o => o.Id); orderConfiguration.Ignore(b => b.DomainEvents); orderConfiguration.Property(o => o.Id) .ForSqlServerUseSequenceHiLo("orderseq", DEFAULT_SCHEMA); orderConfiguration.Property<DateTime>("OrderDate").IsRequired(); orderConfiguration.Property<int?>("BuyerId").IsRequired(false); orderConfiguration.Property<int>("OrderStatusId").IsRequired(); orderConfiguration.Property<int?>("PaymentMethodId").IsRequired(false); var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems)); // DDD Patterns comment: //Set as Field (New since EF 1.1) to access the OrderItem collection property through its field navigation.SetPropertyAccessMode(PropertyAccessMode.Field); orderConfiguration.HasOne(o => o.PaymentMethod) .WithMany() .HasForeignKey("PaymentMethodId") .IsRequired(false) .OnDelete(DeleteBehavior.Restrict); orderConfiguration.HasOne(o => o.Buyer) .WithMany() .IsRequired(false) .HasForeignKey("BuyerId"); orderConfiguration.HasOne(o => o.OrderStatus) .WithMany() .HasForeignKey("OrderStatusId"); } void ConfigureOrderItems(EntityTypeBuilder<OrderItem> orderItemConfiguration) { orderItemConfiguration.ToTable("orderItems", DEFAULT_SCHEMA); orderItemConfiguration.HasKey(o => o.Id); orderItemConfiguration.Ignore(b => b.DomainEvents); orderItemConfiguration.Property(o => o.Id) .ForSqlServerUseSequenceHiLo("orderitemseq"); orderItemConfiguration.Property<int>("OrderId") .IsRequired(); orderItemConfiguration.Property<decimal>("Discount") .IsRequired(); orderItemConfiguration.Property<int>("ProductId") .IsRequired(); orderItemConfiguration.Property<string>("ProductName") .IsRequired(); orderItemConfiguration.Property<decimal>("UnitPrice") .IsRequired(); orderItemConfiguration.Property<int>("Units") .IsRequired(); orderItemConfiguration.Property<string>("PictureUrl") .IsRequired(false); } void ConfigureOrderStatus(EntityTypeBuilder<OrderStatus> orderStatusConfiguration) { orderStatusConfiguration.ToTable("orderstatus", DEFAULT_SCHEMA); orderStatusConfiguration.HasKey(o => o.Id); orderStatusConfiguration.Property(o => o.Id) .HasDefaultValue(1) .ValueGeneratedNever() .IsRequired(); orderStatusConfiguration.Property(o => o.Name) .HasMaxLength(200) .IsRequired(); } void ConfigureCardTypes(EntityTypeBuilder<CardType> cardTypesConfiguration) { cardTypesConfiguration.ToTable("cardtypes", DEFAULT_SCHEMA); cardTypesConfiguration.HasKey(ct => ct.Id); cardTypesConfiguration.Property(ct => ct.Id) .HasDefaultValue(1) .ValueGeneratedNever() .IsRequired(); cardTypesConfiguration.Property(ct => ct.Name) .HasMaxLength(200) .IsRequired(); } public async Task<int> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) { // Dispatch Domain Events collection. // Choices: // A) Right BEFORE committing data (EF SaveChanges) into the DB will make a single transaction including // side effects from the domain event handlers which are using the same DbContext with "InstancePerLifetimeScope" or "scoped" lifetime // B) Right AFTER committing data (EF SaveChanges) into the DB will make multiple transactions. // You will need to handle eventual consistency and compensatory actions in case of failures in any of the Handlers. await _mediator.DispatchDomainEventsAsync(this); // After executing this line all the changes performed thought the DbContext will be commited var result = await base.SaveChangesAsync(); return result; } } }