Advanced Modeling in .NET

This section delves into sophisticated modeling techniques within the .NET ecosystem, enabling developers to build robust, scalable, and maintainable applications. We will explore advanced concepts such as Domain-Driven Design (DDD), Entity Framework Core advanced features, and asynchronous modeling patterns.

Domain-Driven Design (DDD) Principles

Domain-Driven Design is an approach to software development that emphasizes the understanding and modeling of the core business domain. It focuses on collaboration between technical and business experts to iteratively refine a conceptual model that represents the business domain. Key DDD strategic patterns include:

Tactical DDD patterns provide building blocks for creating expressive domain models:

Example: Aggregate Design

Consider an e-commerce system. An Order Aggregate Root might contain multiple OrderItem entities. Business rules, such as preventing an order from being modified after it's been shipped, are enforced at the Aggregate Root level.


public class Order
{
    public Guid Id { get; private set; }
    private List<OrderItem> _items = new List<OrderItem>();
    public OrderStatus Status { get; private set; }

    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();

    public void AddItem(Product product, int quantity)
    {
        if (Status == OrderStatus.Shipped)
        {
            throw new InvalidOperationException("Cannot add items to a shipped order.");
        }
        // ... business logic to add item, check stock, etc.
        _items.Add(new OrderItem(product, quantity));
    }

    public void MarkAsShipped()
    {
        if (Status != OrderStatus.Processing)
        {
            throw new InvalidOperationException("Order cannot be shipped in its current state.");
        }
        Status = OrderStatus.Shipped;
        // Raise DomainEvent: OrderShipped
    }
}

public class OrderItem
{
    public Guid Id { get; private set; }
    public Product Product { get; private set; }
    public int Quantity { get; private set; }

    // Constructor and other logic
}

public enum OrderStatus { Pending, Processing, Shipped, Cancelled }
                

Entity Framework Core Advanced Features

Entity Framework Core (EF Core) offers powerful features that complement advanced modeling techniques:

Example: Using Owned Entities and Value Converters

Here's how you might configure an Address as an owned entity and use a Value Converter for a custom postal code format.


// Model definition
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address ShippingAddress { get; set; } // Owned Entity
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public PostalCode ZipCode { get; set; } // Custom Value Object with Value Converter
}

public class PostalCode
{
    public string Value { get; private set; }
    public PostalCode(string value) { Value = value; }
    // ... validation
}

// EF Core Configuration
public class AppDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configure Address as Owned Entity
        modelBuilder.Entity<Customer>().OwnsOne(c => c.ShippingAddress, sa =>
        {
            sa.Property(a => a.Street).HasColumnName("ShippingStreet");
            sa.Property(a => a.City).HasColumnName("ShippingCity");

            // Configure PostalCode Value Object with Value Converter
            sa.OwnsOne(a => a.ZipCode, zip =>
            {
                zip.Property(pc => pc.Value).HasColumnName("ShippingZipCode");
            });
        });

        // Example Value Converter for PostalCode (simplistic)
        var postalCodeConverter = new ValueConverter<PostalCode, string>(
            v => v.Value,
            v => new PostalCode(v)
        );

        modelBuilder.Entity<Customer>().Property("ShippingAddress.ZipCode.Value")
            .HasConversion(postalCodeConverter);
    }

    // ... DbContext setup
}
                

Asynchronous Modeling Patterns

Asynchronous programming is critical for building responsive and scalable .NET applications, especially in I/O-bound scenarios like web requests or database operations. Key concepts include:

Example: Asynchronous Repository Method


public interface IProductRepository
{
    Task<Product> GetByIdAsync(int id);
    Task AddAsync(Product product);
    Task<IEnumerable<Product>> GetAllAsync();
}

public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;

    public ProductRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task<Product> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id);
    }

    public async Task AddAsync(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        return await _context.Products.ToListAsync();
    }
}
                

Further Reading