FS.EntityFramework.Library.GuidV7 9.0.7

dotnet add package FS.EntityFramework.Library.GuidV7 --version 9.0.7
                    
NuGet\Install-Package FS.EntityFramework.Library.GuidV7 -Version 9.0.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FS.EntityFramework.Library.GuidV7" Version="9.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FS.EntityFramework.Library.GuidV7" Version="9.0.7" />
                    
Directory.Packages.props
<PackageReference Include="FS.EntityFramework.Library.GuidV7" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FS.EntityFramework.Library.GuidV7 --version 9.0.7
                    
#r "nuget: FS.EntityFramework.Library.GuidV7, 9.0.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FS.EntityFramework.Library.GuidV7@9.0.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FS.EntityFramework.Library.GuidV7&version=9.0.7
                    
Install as a Cake Addin
#tool nuget:?package=FS.EntityFramework.Library.GuidV7&version=9.0.7
                    
Install as a Cake Tool

FS.EntityFramework.Library

NuGet Version NuGet Downloads GitHub License GitHub Stars

A comprehensive, production-ready Entity Framework Core library providing Repository pattern, Unit of Work, Specification pattern, dynamic filtering, pagination support, Domain Events, Domain-Driven Design (DDD), Fluent Configuration API, and modular ID generation strategies for .NET applications.

🌟 Why Choose FS.EntityFramework.Library?

graph TB
    A[🎯 FS.EntityFramework.Library] --> B[πŸ—οΈ Repository Pattern]
    A --> C[πŸ”„ Unit of Work]
    A --> D[πŸ“‹ Specification Pattern]
    A --> E[πŸ” Dynamic Filtering]
    A --> F[πŸ“„ Pagination]
    A --> G[🎭 Domain Events]
    A --> H[βš™οΈ Fluent Configuration]
    A --> I[πŸ”‘ Modular ID Generation]
    A --> J[πŸ“Š Audit Tracking]
    A --> K[πŸ—‘οΈ Soft Delete & Restore]
    A --> L[πŸ›οΈ Domain-Driven Design]
    A --> M[🏭 Aggregate Roots]
    A --> N[πŸ“ Value Objects]
    A --> O[βš–οΈ Business Rules]

πŸ“‹ Table of Contents

πŸš€ Quick Start

1️⃣ Basic Setup (30 seconds)

// 1. Install NuGet package
dotnet add package FS.EntityFramework.Library

// 2. Configure your DbContext
services.AddDbContext<YourDbContext>(options =>
    options.UseSqlServer(connectionString));

// 3. Add FS.EntityFramework (One line setup!)
services.AddFSEntityFramework<YourDbContext>()
    .Build();

// 4. Create your entities
public class Product : BaseAuditableEntity<int>
{
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

// 5. Use in your services
public class ProductService
{
    private readonly IUnitOfWork _unitOfWork;
    
    public ProductService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    
    public async Task<Product> CreateProductAsync(string name, decimal price)
    {
        var repository = _unitOfWork.GetRepository<Product, int>();
        var product = new Product { Name = name, Price = price };
        
        await repository.AddAsync(product);
        await _unitOfWork.SaveChangesAsync();
        
        return product;
    }
}
services.AddFSEntityFramework<YourDbContext>()
    .WithAudit()                          // πŸ“Š Automatic audit tracking
        .UsingHttpContext()               // πŸ‘€ User tracking via HTTP context
    .WithDomainEvents()                   // 🎭 Domain events support
        .UsingDefaultDispatcher()         // πŸ“‘ Default event dispatcher
        .WithAutoHandlerDiscovery()       // πŸ” Auto-discover event handlers
    .Complete()                           // βœ… Complete domain events setup
    .WithSoftDelete()                     // πŸ—‘οΈ Soft delete functionality
    .WithDomainDrivenDesign()             // πŸ›οΈ DDD support
        .WithAutoRepositoryDiscovery()    // πŸ—οΈ Auto-discover domain repositories
        .WithDomainValidation()           // βš–οΈ Domain validation services
    .Complete()                           // βœ… Complete DDD setup
    .WithGuidV7()                         // πŸ”‘ GUID V7 ID generation
    .ValidateConfiguration()              // βœ… Validate setup
    .Build();

πŸ’Ύ Installation

Core Package

# Core library with all essential features including DDD
dotnet add package FS.EntityFramework.Library

Extension Packages (Optional)

# GUID Version 7 ID generation (.NET 9+)
dotnet add package FS.EntityFramework.Library.GuidV7

# ULID ID generation
dotnet add package FS.EntityFramework.Library.UlidGenerator

Requirements

  • .NET 9.0 or later
  • Entity Framework Core 9.0.7 or later
  • Microsoft.AspNetCore.Http.Abstractions 2.3.0 or later (for HttpContext support)

βš™οΈ Configuration

The Fluent Configuration API provides an intuitive, chainable way to configure the library with better readability and validation.

graph LR
    A[AddFSEntityFramework] --> B[WithAudit]
    A --> C[WithDomainEvents]
    A --> D[WithSoftDelete]
    A --> E[WithIdGeneration]
    A --> F[WithDomainDrivenDesign]
    B --> G[UsingHttpContext]
    B --> H[UsingUserProvider]
    B --> I[UsingUserContext]
    C --> J[UsingDefaultDispatcher]
    C --> K[UsingCustomDispatcher]
    C --> L[WithAutoHandlerDiscovery]
    F --> M[WithAutoRepositoryDiscovery]
    F --> N[WithDomainValidation]
    J --> O[Complete]
    K --> O
    L --> O
    M --> O
    N --> O
    O --> P[Build]
    D --> P
    E --> P
    G --> P
    H --> P
    I --> P
Basic Configuration Options
// πŸ”§ Minimal setup
services.AddFSEntityFramework<YourDbContext>()
    .Build();

// πŸ“Š With audit tracking
services.AddFSEntityFramework<YourDbContext>()
    .WithAudit()
        .UsingHttpContext()
    .Build();

// 🎭 With domain events
services.AddFSEntityFramework<YourDbContext>()
    .WithDomainEvents()
        .UsingDefaultDispatcher()
        .WithAutoHandlerDiscovery()
    .Complete()
    .Build();

// πŸ—‘οΈ With soft delete
services.AddFSEntityFramework<YourDbContext>()
    .WithSoftDelete()
    .Build();

// πŸ›οΈ With Domain-Driven Design
services.AddFSEntityFramework<YourDbContext>()
    .WithDomainDrivenDesign()
        .WithAutoRepositoryDiscovery()
        .WithDomainValidation()
    .Complete()
    .Build();
Advanced DDD Configuration
services.AddFSEntityFramework<YourDbContext>()
    // πŸ“Š Audit Configuration
    .WithAudit()
        .UsingUserProvider(provider => 
        {
            var userService = provider.GetService<ICurrentUserService>();
            return userService?.GetCurrentUserId();
        })
    
    // 🎭 Domain Events Configuration
    .WithDomainEvents()
        .UsingDefaultDispatcher()
        .WithAutoHandlerDiscovery(typeof(ProductCreatedEvent).Assembly)
        .WithAttributeBasedDiscovery(Assembly.GetExecutingAssembly())
        .WithHandler<ProductCreatedEvent, ProductCreatedEventHandler>()
    .Complete()
    
    // πŸ›οΈ Domain-Driven Design Configuration
    .WithDomainDrivenDesign()
        .WithAutoRepositoryDiscovery(Assembly.GetExecutingAssembly())
        .WithRepository<ProductAggregate, Guid>()
        .WithCustomRepository<OrderAggregate, Guid, OrderRepository>()
        .WithDomainValidation()
    .Complete()
    
    // πŸ—‘οΈ Soft Delete Configuration
    .WithSoftDelete()
    
    // πŸ”‘ ID Generation Configuration
    .WithIdGeneration()
        .WithGenerator<Guid, MyCustomGuidGenerator>()
    .Complete()
    
    // 🎯 Custom Repository Registration
    .WithCustomRepository<Product, int, ProductRepository>()
    .WithRepositoriesFromAssembly(Assembly.GetExecutingAssembly())
    
    // βš™οΈ Additional Services
    .WithServices(services =>
    {
        services.AddScoped<IMyCustomService, MyCustomService>();
    })
    
    // πŸ” Conditional Configuration
    .When(isDevelopment, builder =>
        builder.WithDetailedLogging(enableSensitiveDataLogging: true))
    
    // βœ… Validation & Build
    .ValidateConfiguration()
    .Build();

πŸ”§ Classic Configuration

The original configuration methods are still supported for backward compatibility:

// Basic setup without audit
services.AddGenericUnitOfWork<YourDbContext>();

// With audit support using user service
services.AddGenericUnitOfWorkWithAudit<YourDbContext>(
    provider => provider.GetRequiredService<ICurrentUserService>().UserId);

// With Domain-Driven Design services
services.AddDomainServices();
services.AddDomainRepositoriesFromCallingAssembly();

// With domain events
services.AddDomainEvents();
services.AddDomainEventHandlersFromAssembly(typeof(ProductCreatedEvent).Assembly);

πŸ—οΈ Core Concepts

πŸ“¦ Base Entities

The library provides several base entity classes to build your domain models:

// 1️⃣ Basic Entity (ID + Domain Events)
public class Tag : BaseEntity<int>
{
    public string Name { get; set; } = string.Empty;
    public string Color { get; set; } = "#000000";
}

// 2️⃣ Auditable Entity (ID + Audit Properties + Domain Events)
public class Category : BaseAuditableEntity<int>
{
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    
    // Automatic properties:
    // - CreatedAt, CreatedBy
    // - UpdatedAt, UpdatedBy
}

// 3️⃣ Full-Featured Entity (ID + Audit + Soft Delete + Domain Events)
public class Product : BaseAuditableEntity<int>, ISoftDelete
{
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public string Description { get; set; } = string.Empty;
    public int CategoryId { get; set; }
    
    // Navigation property
    public Category Category { get; set; } = null!;
    
    // ISoftDelete properties (automatic)
    public bool IsDeleted { get; set; }
    public DateTime? DeletedAt { get; set; }
    public string? DeletedBy { get; set; }
    
    // Factory method with domain events
    public static Product Create(string name, decimal price, string description, int categoryId)
    {
        var product = new Product
        {
            Name = name,
            Price = price,
            Description = description,
            CategoryId = categoryId
        };
        
        // Raise domain event
        product.AddDomainEvent(new ProductCreatedEvent(product.Id, name, price));
        
        return product;
    }
    
    public void UpdatePrice(decimal newPrice)
    {
        var oldPrice = Price;
        Price = newPrice;
        
        // Raise domain event
        AddDomainEvent(new ProductPriceChangedEvent(Id, oldPrice, newPrice));
    }
}

πŸ›οΈ Domain-Driven Design (DDD)

This library provides comprehensive support for Domain-Driven Design patterns and practices, enabling you to build rich domain models with proper encapsulation and business logic.

🏭 Aggregate Roots

Aggregate Roots are the entry points to your aggregates and ensure consistency boundaries:

// βœ… Product Aggregate Root
public class ProductAggregate : AggregateRoot<Guid>
{
    private readonly List<ProductVariant> _variants = new();
    
    public string Name { get; private set; } = string.Empty;
    public Money Price { get; private set; } = Money.Zero;
    public ProductStatus Status { get; private set; } = ProductStatus.Draft;
    
    // Read-only access to variants
    public IReadOnlyCollection<ProductVariant> Variants => _variants.AsReadOnly();
    
    // Factory method enforcing business rules
    public static ProductAggregate Create(string name, Money price)
    {
        DomainGuard.AgainstNullOrWhiteSpace(name, nameof(name));
        DomainGuard.AgainstNegativeOrZero(price.Amount, nameof(price));
        
        var product = new ProductAggregate(Guid.CreateVersion7())
        {
            Name = name,
            Price = price,
            Status = ProductStatus.Draft
        };
        
        // Raise domain event
        product.RaiseDomainEvent(new ProductCreatedEvent(product.Id, name, price.Amount));
        
        return product;
    }
    
    // Business method with domain logic
    public void PublishProduct()
    {
        CheckRule(new ProductMustHaveVariantsRule(_variants));
        CheckRule(new ProductMustHaveValidPriceRule(Price));
        
        Status = ProductStatus.Published;
        IncrementVersion();
        
        RaiseDomainEvent(new ProductPublishedEvent(Id, Name));
    }
    
    public void AddVariant(string variantName, Money additionalPrice)
    {
        DomainGuard.AgainstNullOrWhiteSpace(variantName, nameof(variantName));
        
        var variant = new ProductVariant(variantName, additionalPrice);
        _variants.Add(variant);
        
        RaiseDomainEvent(new ProductVariantAddedEvent(Id, variant.Id, variantName));
    }
}

// βœ… Order Aggregate Root with complex business logic
public class OrderAggregate : AggregateRoot<Guid>
{
    private readonly List<OrderItem> _items = new();
    
    public CustomerId CustomerId { get; private set; }
    public OrderStatus Status { get; private set; }
    public Money TotalAmount { get; private set; }
    public DateTime OrderDate { get; private set; }
    
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
    
    public static OrderAggregate Create(CustomerId customerId)
    {
        var order = new OrderAggregate(Guid.CreateVersion7())
        {
            CustomerId = customerId,
            Status = OrderStatus.Pending,
            TotalAmount = Money.Zero,
            OrderDate = DateTime.UtcNow
        };
        
        order.RaiseDomainEvent(new OrderCreatedEvent(order.Id, customerId));
        
        return order;
    }
    
    public void AddItem(ProductId productId, Quantity quantity, Money unitPrice)
    {
        CheckRule(new OrderMustBeInPendingStatusRule(Status));
        
        var existingItem = _items.FirstOrDefault(i => i.ProductId == productId);
        if (existingItem != null)
        {
            existingItem.UpdateQuantity(existingItem.Quantity + quantity);
        }
        else
        {
            var item = new OrderItem(productId, quantity, unitPrice);
            _items.Add(item);
        }
        
        RecalculateTotal();
        RaiseDomainEvent(new OrderItemAddedEvent(Id, productId, quantity));
    }
    
    public void ConfirmOrder()
    {
        CheckRule(new OrderMustHaveItemsRule(_items));
        CheckRule(new OrderTotalMustBePositiveRule(TotalAmount));
        
        Status = OrderStatus.Confirmed;
        IncrementVersion();
        
        RaiseDomainEvent(new OrderConfirmedEvent(Id, TotalAmount.Amount));
    }
    
    private void RecalculateTotal()
    {
        TotalAmount = Money.FromDecimal(_items.Sum(i => i.TotalPrice.Amount));
    }
}

πŸ“ Value Objects

Value Objects encapsulate business concepts and ensure type safety:

// βœ… Money Value Object
public class Money : ValueObject
{
    public decimal Amount { get; }
    public string Currency { get; }
    
    public Money(decimal amount, string currency = "USD")
    {
        DomainGuard.AgainstNegative(amount, nameof(amount));
        DomainGuard.AgainstNullOrWhiteSpace(currency, nameof(currency));
        
        Amount = amount;
        Currency = currency;
    }
    
    public static Money Zero => new(0);
    public static Money FromDecimal(decimal amount) => new(amount);
    
    // Value object operations
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new DomainInvariantViolationException("Cannot add money with different currencies");
        
        return new Money(Amount + other.Amount, Currency);
    }
    
    public Money Multiply(decimal factor) => new(Amount * factor, Currency);
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Amount;
        yield return Currency;
    }
    
    // Operators
    public static Money operator +(Money left, Money right) => left.Add(right);
    public static Money operator *(Money money, decimal factor) => money.Multiply(factor);
}

// βœ… Strongly Typed IDs
public class CustomerId : ValueObject
{
    public Guid Value { get; }
    
    public CustomerId(Guid value)
    {
        DomainGuard.Against(value == Guid.Empty, "Customer ID cannot be empty");
        Value = value;
    }
    
    public static implicit operator Guid(CustomerId customerId) => customerId.Value;
    public static implicit operator CustomerId(Guid value) => new(value);
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

// βœ… Address Value Object
public class Address : ValueObject
{
    public string Street { get; }
    public string City { get; }
    public string PostalCode { get; }
    public string Country { get; }
    
    public Address(string street, string city, string postalCode, string country)
    {
        DomainGuard.AgainstNullOrWhiteSpace(street, nameof(street));
        DomainGuard.AgainstNullOrWhiteSpace(city, nameof(city));
        DomainGuard.AgainstNullOrWhiteSpace(postalCode, nameof(postalCode));
        DomainGuard.AgainstNullOrWhiteSpace(country, nameof(country));
        
        Street = street;
        City = city;
        PostalCode = postalCode;
        Country = country;
    }
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Street;
        yield return City;
        yield return PostalCode;
        yield return Country;
    }
}

βš–οΈ Business Rules & Domain Guard

Implement business rules and domain validation:

// βœ… Business Rules
public class ProductMustHaveVariantsRule : BusinessRule
{
    private readonly IReadOnlyCollection<ProductVariant> _variants;
    
    public ProductMustHaveVariantsRule(IReadOnlyCollection<ProductVariant> variants)
    {
        _variants = variants;
    }
    
    public override bool IsBroken() => _variants.Count == 0;
    
    public override string Message => "Product must have at least one variant to be published";
    
    public override string ErrorCode => "PRODUCT_NO_VARIANTS";
}

public class OrderMustHaveItemsRule : BusinessRule
{
    private readonly IReadOnlyCollection<OrderItem> _items;
    
    public OrderMustHaveItemsRule(IReadOnlyCollection<OrderItem> items)
    {
        _items = items;
    }
    
    public override bool IsBroken() => _items.Count == 0;
    
    public override string Message => "Order must have at least one item";
    
    public override string ErrorCode => "ORDER_NO_ITEMS";
}

// βœ… Using Domain Guard
public class Customer : AggregateRoot<CustomerId>
{
    public string Name { get; private set; } = string.Empty;
    public Email Email { get; private set; } = null!;
    public Address Address { get; private set; } = null!;
    
    public static Customer Create(string name, string email, Address address)
    {
        // Domain Guard validations
        DomainGuard.AgainstNullOrWhiteSpace(name, nameof(name));
        DomainGuard.AgainstNull(address, nameof(address));
        
        var customer = new Customer(new CustomerId(Guid.CreateVersion7()))
        {
            Name = name,
            Email = Email.Create(email),
            Address = address
        };
        
        customer.RaiseDomainEvent(new CustomerCreatedEvent(customer.Id, name, email));
        
        return customer;
    }
    
    public void UpdateAddress(Address newAddress)
    {
        DomainGuard.AgainstNull(newAddress, nameof(newAddress));
        
        var oldAddress = Address;
        Address = newAddress;
        
        RaiseDomainEvent(new CustomerAddressChangedEvent(Id, oldAddress, newAddress));
    }
}

🎯 Domain Specifications

Build reusable domain logic with specifications:

// βœ… Domain Specifications
public class ActiveCustomersSpecification : DomainSpecification<Customer>
{
    public override bool IsSatisfiedBy(Customer candidate)
    {
        return candidate.IsActive && !candidate.IsDeleted;
    }
    
    public override Expression<Func<Customer, bool>> ToExpression()
    {
        return customer => customer.IsActive && !customer.IsDeleted;
    }
}

public class HighValueCustomersSpecification : DomainSpecification<Customer>
{
    private readonly decimal _minimumOrderValue;
    
    public HighValueCustomersSpecification(decimal minimumOrderValue)
    {
        _minimumOrderValue = minimumOrderValue;
    }
    
    public override bool IsSatisfiedBy(Customer candidate)
    {
        return candidate.TotalOrderValue >= _minimumOrderValue;
    }
    
    public override Expression<Func<Customer, bool>> ToExpression()
    {
        return customer => customer.TotalOrderValue >= _minimumOrderValue;
    }
}

// βœ… Using Composite Specifications
public class CustomerQueryService
{
    private readonly IDomainRepository<Customer, CustomerId> _customerRepository;
    
    public CustomerQueryService(IDomainRepository<Customer, CustomerId> customerRepository)
    {
        _customerRepository = customerRepository;
    }
    
    public async Task<IEnumerable<Customer>> GetActiveHighValueCustomersAsync(decimal minimumValue)
    {
        var activeSpec = new ActiveCustomersSpecification();
        var highValueSpec = new HighValueCustomersSpecification(minimumValue);
        
        // Combine specifications
        var combinedSpec = activeSpec.And(highValueSpec);
        
        return await _customerRepository.FindAsync(combinedSpec);
    }
}

πŸ—οΈ Domain Repositories

Domain repositories provide aggregate-specific operations:

// βœ… Domain Repository Interface
public interface IProductRepository : IDomainRepository<ProductAggregate, Guid>
{
    Task<IEnumerable<ProductAggregate>> GetByStatusAsync(ProductStatus status, CancellationToken cancellationToken = default);
    Task<ProductAggregate?> GetByNameAsync(string name, CancellationToken cancellationToken = default);
    Task<bool> ExistsWithNameAsync(string name, CancellationToken cancellationToken = default);
}

// βœ… Domain Repository Implementation
public class ProductRepository : DomainRepository<ProductAggregate, Guid>, IProductRepository
{
    public ProductRepository(IRepository<ProductAggregate, Guid> efRepository) 
        : base(efRepository)
    {
    }
    
    public async Task<IEnumerable<ProductAggregate>> GetByStatusAsync(ProductStatus status, CancellationToken cancellationToken = default)
    {
        var specification = new ProductsByStatusSpecification(status);
        return await FindAsync(specification, cancellationToken);
    }
    
    public async Task<ProductAggregate?> GetByNameAsync(string name, CancellationToken cancellationToken = default)
    {
        var specification = new ProductByNameSpecification(name);
        var products = await FindAsync(specification, cancellationToken);
        return products.FirstOrDefault();
    }
    
    public async Task<bool> ExistsWithNameAsync(string name, CancellationToken cancellationToken = default)
    {
        var specification = new ProductByNameSpecification(name);
        return await AnyAsync(specification, cancellationToken);
    }
}

// βœ… Registration
services.AddFSEntityFramework<YourDbContext>()
    .WithDomainDrivenDesign()
        .WithCustomRepository<ProductAggregate, Guid, ProductRepository>()
    .Complete()
    .Build();

πŸ”„ Domain Unit of Work

Coordinate multiple aggregate changes:

// βœ… Domain Service using Domain Unit of Work
public class OrderProcessingService
{
    private readonly IDomainUnitOfWork _domainUnitOfWork;
    private readonly IDomainRepository<OrderAggregate, Guid> _orderRepository;
    private readonly IDomainRepository<ProductAggregate, Guid> _productRepository;
    private readonly IDomainRepository<Customer, CustomerId> _customerRepository;
    
    public OrderProcessingService(
        IDomainUnitOfWork domainUnitOfWork,
        IDomainRepository<OrderAggregate, Guid> orderRepository,
        IDomainRepository<ProductAggregate, Guid> productRepository,
        IDomainRepository<Customer, CustomerId> customerRepository)
    {
        _domainUnitOfWork = domainUnitOfWork;
        _orderRepository = orderRepository;
        _productRepository = productRepository;
        _customerRepository = customerRepository;
    }
    
    public async Task<OrderAggregate> ProcessOrderAsync(ProcessOrderCommand command)
    {
        // Begin domain transaction
        await _domainUnitOfWork.BeginTransactionAsync();
        
        try
        {
            // Load aggregates
            var customer = await _customerRepository.GetByIdRequiredAsync(command.CustomerId);
            var order = OrderAggregate.Create(command.CustomerId);
            
            // Process order items
            foreach (var item in command.Items)
            {
                var product = await _productRepository.GetByIdRequiredAsync(item.ProductId);
                
                // Domain logic - check business rules
                DomainGuard.Against(!product.IsAvailable, "Product is not available");
                
                order.AddItem(item.ProductId, item.Quantity, product.Price);
            }
            
            // Apply customer discount if applicable
            if (customer.IsPremium)
            {
                order.ApplyDiscount(customer.DiscountPercentage);
            }
            
            // Confirm the order
            order.ConfirmOrder();
            
            // Register aggregates for domain event publishing
            _domainUnitOfWork.RegisterAggregate(order);
            _domainUnitOfWork.RegisterAggregate(customer);
            
            // Save aggregate
            await _orderRepository.AddAsync(order);
            
            // Save changes and publish domain events
            await _domainUnitOfWork.SaveChangesAsync();
            
            // Commit transaction
            await _domainUnitOfWork.CommitTransactionAsync();
            
            return order;
        }
        catch
        {
            await _domainUnitOfWork.RollbackTransactionAsync();
            throw;
        }
    }
}

🎭 Domain Events

Domain Events enable loose coupling and implementing cross-cutting concerns in a clean way.

graph LR
    A[Aggregate Root] -->|Raises| B[Domain Event]
    B --> C[Event Dispatcher]
    C --> D[Event Handler 1]
    C --> E[Event Handler 2]
    C --> F[Event Handler N]
    
    D --> G[Send Email]
    E --> H[Update Cache]
    F --> I[Log Audit]

Domain Event Implementation

// βœ… 1. Define Domain Events
public class ProductCreatedEvent : DomainEvent
{
    public ProductCreatedEvent(Guid productId, string productName, decimal price)
    {
        ProductId = productId;
        ProductName = productName;
        Price = price;
    }
    
    public Guid ProductId { get; }
    public string ProductName { get; }
    public decimal Price { get; }
}

public class OrderConfirmedEvent : DomainEvent
{
    public OrderConfirmedEvent(Guid orderId, decimal totalAmount, CustomerId customerId)
    {
        OrderId = orderId;
        TotalAmount = totalAmount;
        CustomerId = customerId;
    }
    
    public Guid OrderId { get; }
    public decimal TotalAmount { get; }
    public CustomerId CustomerId { get; }
}

// βœ… 2. Create Event Handlers
public class ProductCreatedEventHandler : IDomainEventHandler<ProductCreatedEvent>
{
    private readonly ILogger<ProductCreatedEventHandler> _logger;
    private readonly IEmailService _emailService;
    private readonly ICacheService _cacheService;
    
    public ProductCreatedEventHandler(
        ILogger<ProductCreatedEventHandler> logger,
        IEmailService emailService,
        ICacheService cacheService)
    {
        _logger = logger;
        _emailService = emailService;
        _cacheService = cacheService;
    }
    
    public async Task Handle(ProductCreatedEvent domainEvent, CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Product created: {ProductName} with price: {Price}", 
            domainEvent.ProductName, domainEvent.Price);
        
        // Invalidate cache
        await _cacheService.InvalidateAsync("products", cancellationToken);
        
        // Send notification email
        await _emailService.SendProductCreatedNotificationAsync(
            domainEvent.ProductName, 
            domainEvent.Price, 
            cancellationToken);
    }
}

// βœ… 3. Order Confirmed Handler
[DomainEventHandler(ServiceLifetime = ServiceLifetime.Scoped, Order = 1)]
public class OrderConfirmedEventHandler : IDomainEventHandler<OrderConfirmedEvent>
{
    private readonly IEmailService _emailService;
    private readonly IInventoryService _inventoryService;
    private readonly IDomainRepository<Customer, CustomerId> _customerRepository;
    
    public OrderConfirmedEventHandler(
        IEmailService emailService,
        IInventoryService inventoryService,
        IDomainRepository<Customer, CustomerId> customerRepository)
    {
        _emailService = emailService;
        _inventoryService = inventoryService;
        _customerRepository = customerRepository;
    }
    
    public async Task Handle(OrderConfirmedEvent domainEvent, CancellationToken cancellationToken = default)
    {
        // Update customer statistics
        var customer = await _customerRepository.GetByIdRequiredAsync(domainEvent.CustomerId, cancellationToken);
        customer.UpdateOrderStatistics(domainEvent.TotalAmount);
        
        // Reserve inventory
        await _inventoryService.ReserveInventoryAsync(domainEvent.OrderId, cancellationToken);
        
        // Send confirmation email
        await _emailService.SendOrderConfirmationAsync(domainEvent.OrderId, cancellationToken);
    }
}

Domain Event Configuration

// βœ… Automatic Handler Discovery (Simplest)
services.AddFSEntityFramework<YourDbContext>()
    .WithDomainEvents()
        .UsingDefaultDispatcher()
        .WithAutoHandlerDiscovery() // Scans calling assembly
    .Complete()
    .Build();

// βœ… Multiple Assembly Discovery
services.AddFSEntityFramework<YourDbContext>()
    .WithDomainEvents()
        .UsingDefaultDispatcher()
        .WithAutoHandlerDiscovery(
            typeof(ProductCreatedEvent).Assembly,
            typeof(OrderCreatedEvent).Assembly)
    .Complete()
    .Build();

// βœ… Attribute-Based Discovery
services.AddFSEntityFramework<YourDbContext>()
    .WithDomainEvents()
        .UsingDefaultDispatcher()
        .WithAttributeBasedDiscovery(Assembly.GetExecutingAssembly())
    .Complete()
    .Build();

// βœ… Custom Handler Registration
services.AddFSEntityFramework<YourDbContext>()
    .WithDomainEvents()
        .UsingDefaultDispatcher()
        .WithHandler<ProductCreatedEvent, ProductCreatedEventHandler>()
        .WithHandler<OrderConfirmedEvent, OrderConfirmedEventHandler>()
    .Complete()
    .Build();

πŸ“Š Audit Tracking

Automatic audit tracking for entity lifecycle events with flexible user context providers.

graph TB
    A[Entity Save Operation] --> B{Audit Enabled?}
    B -->|Yes| C[AuditInterceptor]
    B -->|No| D[Regular Save]
    
    C --> E{Entity State?}
    E -->|Added| F[Set CreatedAt, CreatedBy]
    E -->|Modified| G[Set UpdatedAt, UpdatedBy]
    E -->|Deleted| H{Soft Delete?}
    
    H -->|Yes| I[Set IsDeleted, DeletedAt, DeletedBy]
    H -->|No| J[Hard Delete]
    
    F --> K[Get Current User]
    G --> K
    I --> K
    
    K --> L[User Context Provider]
    L --> M[HttpContext]
    L --> N[Custom Service]
    L --> O[Static User]
    L --> P[Interface-Based]

Audit Configuration Examples

// βœ… 1. HttpContext-based User Tracking (Web Apps)
services.AddFSEntityFramework<YourDbContext>()
    .WithAudit()
        .UsingHttpContext() // Uses NameIdentifier claim by default
    .Build();

// βœ… 2. Custom Claim Type
services.AddFSEntityFramework<YourDbContext>()
    .WithAudit()
        .UsingHttpContext("custom-user-id-claim")
    .Build();

// βœ… 3. Custom User Provider
services.AddFSEntityFramework<YourDbContext>()
    .WithAudit()
        .UsingUserProvider(provider =>
        {
            var userService = provider.GetService<ICurrentUserService>();
            return userService?.GetCurrentUserId();
        })
    .Build();

// βœ… 4. Interface-based User Context
public class ApplicationUserContext : IUserContext
{
    private readonly ICurrentUserService _userService;
    
    public ApplicationUserContext(ICurrentUserService userService)
    {
        _userService = userService;
    }
    
    public string? CurrentUser => _userService.GetCurrentUserId();
}

services.AddScoped<IUserContext, ApplicationUserContext>();
services.AddFSEntityFramework<YourDbContext>()
    .WithAudit()
        .UsingUserContext<IUserContext>()
    .Build();

πŸ—‘οΈ Soft Delete & Restore

Comprehensive soft delete implementation with restore functionality and query filters.

graph TB
    A[Entity Delete Operation] --> B{Implements ISoftDelete?}
    B -->|Yes| C[Soft Delete]
    B -->|No| D[Hard Delete]
    
    C --> E[Set IsDeleted = true]
    E --> F[Set DeletedAt = DateTime.UtcNow]
    F --> G[Set DeletedBy = CurrentUser]
    G --> H[Update Entity]
    
    D --> I[Remove from Database]
    
    J[Query Operations] --> K[Global Query Filter]
    K --> L[WHERE IsDeleted = false]
    
    M[Special Queries] --> N[IncludeDeleted]
    M --> O[OnlyDeleted]
    
    P[Restore Operation] --> Q[Set IsDeleted = false]
    Q --> R[Set DeletedAt = null]
    R --> S[Set DeletedBy = null]
    S --> T[Update Entity]

Soft Delete Configuration

// βœ… Enable Soft Delete with Global Query Filters
services.AddFSEntityFramework<YourDbContext>()
    .WithSoftDelete()  // Automatically applies global query filters
    .Build();

// βœ… Manual Configuration in DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    
    // Apply soft delete query filters to all ISoftDelete entities
    modelBuilder.ApplySoftDeleteQueryFilters();
}

Soft Delete Operations

public class ProductService
{
    private readonly IUnitOfWork _unitOfWork;
    
    public ProductService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    
    // βœ… Soft Delete Operations
    public async Task SoftDeleteProductAsync(int productId)
    {
        var repository = _unitOfWork.GetRepository<Product, int>();
        var product = await repository.GetByIdAsync(productId);
        
        if (product != null)
        {
            // This will soft delete (set IsDeleted=true, DeletedAt=now, DeletedBy=user)
            await repository.DeleteAsync(product, saveChanges: true);
        }
    }
    
    // βœ… Hard Delete (Permanent Removal)
    public async Task HardDeleteProductAsync(int productId)
    {
        var repository = _unitOfWork.GetRepository<Product, int>();
        var product = await repository.GetByIdAsync(productId);
        
        if (product != null)
        {
            // This will permanently remove from database
            await repository.HardDeleteAsync(product, saveChanges: true);
        }
    }
    
    // βœ… Restore Soft Deleted Entity
    public async Task RestoreProductAsync(int productId)
    {
        var repository = _unitOfWork.GetRepository<Product, int>();
        
        // This will restore the soft-deleted product
        await repository.RestoreAsync(productId, saveChanges: true);
    }
}

πŸ” Dynamic Filtering

Powerful dynamic filtering system that allows building complex queries from filter models at runtime.

graph TB
    A[FilterModel] --> B[SearchTerm]
    A --> C[FilterItems]
    
    C --> D[Field Name]
    C --> E[Operator]
    C --> F[Value]
    
    E --> G[equals]
    E --> H[contains]
    E --> I[startswith]
    E --> J[endswith]
    E --> K[greaterthan]
    E --> L[lessthan]
    E --> M[greaterthanorequal]
    E --> N[lessthanorequal]
    E --> O[notequals]
    
    B --> P[Expression Builder]
    D --> P
    E --> P
    F --> P
    
    P --> Q[LINQ Expression]
    Q --> R[Database Query]

Dynamic Filtering Usage

public class ProductFilterService
{
    private readonly IUnitOfWork _unitOfWork;
    
    public ProductFilterService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    
    // βœ… Simple Search Term Filtering
    public async Task<IPaginate<Product>> SearchProductsAsync(string searchTerm, int page, int size)
    {
        var filter = new FilterModel
        {
            SearchTerm = searchTerm // Searches across all string properties
        };
        
        var repository = _unitOfWork.GetRepository<Product, int>();
        return await repository.GetPagedWithFilterAsync(filter, page, size);
    }
    
    // βœ… Complex Multi-Field Filtering
    public async Task<IPaginate<Product>> FilterProductsAsync(ProductFilterRequest request)
    {
        var filter = new FilterModel
        {
            SearchTerm = request.SearchTerm,
            Filters = new List<FilterItem>()
        };
        
        // Price range filtering
        if (request.MinPrice.HasValue)
        {
            filter.Filters.Add(new FilterItem
            {
                Field = nameof(Product.Price),
                Operator = "greaterthanorequal",
                Value = request.MinPrice.Value.ToString()
            });
        }
        
        // Category filtering
        if (request.CategoryId.HasValue)
        {
            filter.Filters.Add(new FilterItem
            {
                Field = nameof(Product.CategoryId),
                Operator = "equals",
                Value = request.CategoryId.Value.ToString()
            });
        }
        
        var repository = _unitOfWork.GetRepository<Product, int>();
        
        return await repository.GetPagedWithFilterAsync(
            filter,
            request.Page,
            request.PageSize,
            orderBy: query => query.OrderByDescending(p => p.CreatedAt),
            includes: new List<Expression<Func<Product, object>>> { p => p.Category }
        );
    }
}

πŸ“„ Pagination

Comprehensive pagination support with metadata and flexible query integration.

// βœ… Simple Pagination
public async Task<IPaginate<Product>> GetProductsPagedAsync(int page, int size)
{
    var repository = _unitOfWork.GetRepository<Product, int>();
    
    return await repository.GetPagedAsync(page, size);
}

// βœ… Pagination with Filtering and Ordering
public async Task<IPaginate<Product>> GetProductsWithCategoryPagedAsync(int page, int size)
{
    var repository = _unitOfWork.GetRepository<Product, int>();
    
    return await repository.GetPagedAsync(
        pageIndex: page,
        pageSize: size,
        predicate: p => p.Price > 100,
        orderBy: query => query.OrderByDescending(p => p.CreatedAt),
        includes: new List<Expression<Func<Product, object>>> { p => p.Category }
    );
}

πŸ”‘ ID Generation

Modular ID generation system supporting pluggable strategies including GUID V7, ULID, and custom generators.

GUID Version 7 Integration

// βœ… Install Extension Package
// dotnet add package FS.EntityFramework.Library.GuidV7

// βœ… Basic GUID V7 Setup (.NET 9+)
services.AddFSEntityFramework<YourDbContext>()
    .WithGuidV7()  // Automatic GUID V7 generation
    .Build();

// βœ… Entity with GUID V7
public class User : BaseAuditableEntity<Guid>
{
    public string Name { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    
    // ID will be automatically generated as GUID V7
}

ULID Integration

// βœ… Install Extension Package
// dotnet add package FS.EntityFramework.Library.UlidGenerator

// βœ… Basic ULID Setup
services.AddFSEntityFramework<YourDbContext>()
    .WithUlid()  // Automatic ULID generation
    .Build();

// βœ… Entity with ULID
public class Order : BaseAuditableEntity<Ulid>, ISoftDelete
{
    public string OrderNumber { get; set; } = string.Empty;
    public decimal TotalAmount { get; set; }
    
    // ID will be automatically generated as ULID
}

Custom ID Generators

// βœ… Custom String ID Generator
public class CustomStringIdGenerator : IIdGenerator<string>
{
    public Type KeyType => typeof(string);
    
    public string Generate()
    {
        return $"CUST_{DateTime.UtcNow:yyyyMMdd}_{Guid.NewGuid():N}";
    }
    
    object IIdGenerator.Generate()
    {
        return Generate();
    }
}

// βœ… Register Custom Generators
services.AddFSEntityFramework<YourDbContext>()
    .WithIdGeneration()
        .WithGenerator<string, CustomStringIdGenerator>()
        .WithGenerator<long, TimestampIdGenerator>()
    .Complete()
    .Build();

πŸ“š Advanced Usage

Using FSDbContext (Automatic Configuration)

// βœ… Using FSDbContext for automatic configuration
public class ApplicationDbContext : FSDbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IServiceProvider serviceProvider) 
        : base(options, serviceProvider)
    {
    }
    
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }
    public DbSet<OrderAggregate> Orders { get; set; }
    
    // FS.EntityFramework configurations are automatically applied
}

Advanced Domain Services

// βœ… Domain Service with Complex Business Logic
public class PricingDomainService
{
    private readonly IDomainRepository<ProductAggregate, Guid> _productRepository;
    private readonly IDomainRepository<Customer, CustomerId> _customerRepository;
    
    public PricingDomainService(
        IDomainRepository<ProductAggregate, Guid> productRepository,
        IDomainRepository<Customer, CustomerId> customerRepository)
    {
        _productRepository = productRepository;
        _customerRepository = customerRepository;
    }
    
    public async Task<Money> CalculatePriceForCustomerAsync(Guid productId, CustomerId customerId)
    {
        var product = await _productRepository.GetByIdRequiredAsync(productId);
        var customer = await _customerRepository.GetByIdRequiredAsync(customerId);
        
        var basePrice = product.Price;
        
        // Apply customer-specific pricing rules
        if (customer.IsPremium)
        {
            basePrice = basePrice * 0.9m; // 10% discount
        }
        
        if (customer.IsVip)
        {
            basePrice = basePrice * 0.85m; // Additional 15% discount (25% total)
        }
        
        return basePrice;
    }
}

🎯 Best Practices

Domain Entity Design Best Practices

// βœ… Well-Designed Aggregate Root
public class ProductAggregate : AggregateRoot<Guid>
{
    private readonly List<ProductVariant> _variants = new();
    private readonly List<ProductReview> _reviews = new();
    
    // Private constructor for EF
    private ProductAggregate() { }
    
    // Constructor for domain logic
    private ProductAggregate(Guid id) : base(id) { }
    
    // Properties with proper encapsulation
    public string Name { get; private set; } = string.Empty;
    public Money Price { get; private set; } = Money.Zero;
    public ProductStatus Status { get; private set; } = ProductStatus.Draft;
    public DateTime? PublishedAt { get; private set; }
    
    // Read-only collections
    public IReadOnlyCollection<ProductVariant> Variants => _variants.AsReadOnly();
    public IReadOnlyCollection<ProductReview> Reviews => _reviews.AsReadOnly();
    
    // Factory method enforcing invariants
    public static ProductAggregate Create(string name, Money price, ProductCategory category)
    {
        DomainGuard.AgainstNullOrWhiteSpace(name, nameof(name));
        DomainGuard.AgainstNull(price, nameof(price));
        DomainGuard.AgainstNegativeOrZero(price.Amount, nameof(price));
        
        var product = new ProductAggregate(Guid.CreateVersion7())
        {
            Name = name,
            Price = price,
            Status = ProductStatus.Draft
        };
        
        // Raise domain event
        product.RaiseDomainEvent(new ProductCreatedEvent(product.Id, name, price.Amount));
        
        return product;
    }
    
    // Business methods with validation and events
    public void UpdatePrice(Money newPrice)
    {
        DomainGuard.AgainstNull(newPrice, nameof(newPrice));
        DomainGuard.AgainstNegativeOrZero(newPrice.Amount, nameof(newPrice));
        
        var oldPrice = Price;
        Price = newPrice;
        
        RaiseDomainEvent(new ProductPriceChangedEvent(Id, oldPrice.Amount, newPrice.Amount));
    }
    
    public void Publish()
    {
        CheckRule(new ProductMustHaveValidPriceRule(Price));
        CheckRule(new ProductMustHaveDescriptionRule(Description));
        CheckRule(new ProductMustHaveVariantsRule(_variants));
        
        Status = ProductStatus.Published;
        PublishedAt = DateTime.UtcNow;
        IncrementVersion();
        
        RaiseDomainEvent(new ProductPublishedEvent(Id, Name));
    }
    
    public void AddVariant(string name, Money additionalPrice)
    {
        DomainGuard.AgainstNullOrWhiteSpace(name, nameof(name));
        DomainGuard.AgainstNull(additionalPrice, nameof(additionalPrice));
        
        CheckRule(new ProductVariantNameMustBeUniqueRule(_variants, name));
        
        var variant = new ProductVariant(name, additionalPrice);
        _variants.Add(variant);
        
        RaiseDomainEvent(new ProductVariantAddedEvent(Id, variant.Id, name));
    }
}

Service Layer Best Practices

// βœ… Application Service with Proper Error Handling
public class ProductApplicationService
{
    private readonly IDomainUnitOfWork _domainUnitOfWork;
    private readonly IDomainRepository<ProductAggregate, Guid> _productRepository;
    private readonly IMapper _mapper;
    private readonly ILogger<ProductApplicationService> _logger;
    
    public ProductApplicationService(
        IDomainUnitOfWork domainUnitOfWork,
        IDomainRepository<ProductAggregate, Guid> productRepository,
        IMapper mapper,
        ILogger<ProductApplicationService> logger)
    {
        _domainUnitOfWork = domainUnitOfWork;
        _productRepository = productRepository;
        _mapper = mapper;
        _logger = logger;
    }
    
    public async Task<ProductDto> CreateProductAsync(CreateProductCommand command)
    {
        try
        {
            await _domainUnitOfWork.BeginTransactionAsync();
            
            // Check business rules at application level
            if (await _productRepository.ExistsWithNameAsync(command.Name))
            {
                throw new BusinessRuleViolationException(
                    new ProductNameMustBeUniqueRule(command.Name));
            }
            
            // Create aggregate using domain factory
            var product = ProductAggregate.Create(
                command.Name,
                Money.FromDecimal(command.Price),
                command.Category);
            
            // Register for domain events
            _domainUnitOfWork.RegisterAggregate(product);
            
            // Save
            await _productRepository.AddAsync(product);
            await _domainUnitOfWork.SaveChangesAsync();
            
            await _domainUnitOfWork.CommitTransactionAsync();
            
            _logger.LogInformation("Product created successfully: {ProductId}", product.Id);
            
            return _mapper.Map<ProductDto>(product);
        }
        catch (BusinessRuleValidationException ex)
        {
            await _domainUnitOfWork.RollbackTransactionAsync();
            _logger.LogWarning("Business rule violation while creating product: {Rule}", ex.BrokenRule.Message);
            throw;
        }
        catch (Exception ex)
        {
            await _domainUnitOfWork.RollbackTransactionAsync();
            _logger.LogError(ex, "Error creating product: {ProductName}", command.Name);
            throw;
        }
    }
}

πŸ”§ Troubleshooting

Common DDD Issues and Solutions

1. Domain Events Not Being Dispatched
// ❌ Problem: Events not dispatched
public class OrderService
{
    public async Task CreateOrderAsync(CreateOrderCommand command)
    {
        var order = OrderAggregate.Create(command.CustomerId);
        await _repository.AddAsync(order);
        await _unitOfWork.SaveChangesAsync(); // Events not dispatched
    }
}

// βœ… Solution: Use Domain Unit of Work
public class OrderService
{
    public async Task CreateOrderAsync(CreateOrderCommand command)
    {
        var order = OrderAggregate.Create(command.CustomerId);
        
        // Register aggregate for event publishing
        _domainUnitOfWork.RegisterAggregate(order);
        
        await _repository.AddAsync(order);
        await _domainUnitOfWork.SaveChangesAsync(); // Events dispatched automatically
    }
}
2. Aggregate Root Version Conflicts
// βœ… Handle optimistic concurrency
public async Task UpdateProductAsync(UpdateProductCommand command)
{
    try
    {
        var product = await _productRepository.GetByIdRequiredAsync(command.ProductId);
        
        // Check version for optimistic concurrency
        if (product.Version != command.ExpectedVersion)
        {
            throw new ConcurrencyException("Product has been modified by another process");
        }
        
        product.UpdateDetails(command.Name, command.Description);
        
        await _domainUnitOfWork.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        throw new ConcurrencyException("Product has been modified by another process");
    }
}

Performance Optimization for DDD

// βœ… Optimized Repository with Specifications
public class OptimizedProductRepository : DomainRepository<ProductAggregate, Guid>
{
    public OptimizedProductRepository(IRepository<ProductAggregate, Guid> efRepository) 
        : base(efRepository)
    {
    }
    
    public async Task<IEnumerable<ProductAggregate>> GetTopProductsAsync(int count)
    {
        // Use projection for better performance
        return await _efRepository.GetQueryable()
            .Where(p => p.Status == ProductStatus.Published)
            .OrderByDescending(p => p.SalesCount)
            .Take(count)
            .Include(p => p.Category) // Only include what's needed
            .AsNoTracking() // Disable tracking for read-only operations
            .ToListAsync();
    }
}

πŸ“– API Reference

Domain-Driven Design Interfaces

IAggregateRoot
public interface IAggregateRoot
{
    IReadOnlyCollection<IDomainEvent> DomainEvents { get; }
    void ClearDomainEvents();
    long Version { get; }
}
IDomainRepository<TAggregate, TKey>
public interface IDomainRepository<TAggregate, in TKey>
    where TAggregate : AggregateRoot<TKey>
    where TKey : IEquatable<TKey>
{
    Task<TAggregate?> GetByIdAsync(TKey id, CancellationToken cancellationToken = default);
    Task<TAggregate> GetByIdRequiredAsync(TKey id, CancellationToken cancellationToken = default);
    Task AddAsync(TAggregate aggregate, CancellationToken cancellationToken = default);
    Task UpdateAsync(TAggregate aggregate, CancellationToken cancellationToken = default);
    Task RemoveAsync(TAggregate aggregate, CancellationToken cancellationToken = default);
    Task<IEnumerable<TAggregate>> FindAsync(ISpecification<TAggregate> specification, CancellationToken cancellationToken = default);
    Task<bool> AnyAsync(ISpecification<TAggregate> specification, CancellationToken cancellationToken = default);
    Task<int> CountAsync(ISpecification<TAggregate> specification, CancellationToken cancellationToken = default);
}
IDomainUnitOfWork
public interface IDomainUnitOfWork : IDisposable
{
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
    Task BeginTransactionAsync(CancellationToken cancellationToken = default);
    Task CommitTransactionAsync(CancellationToken cancellationToken = default);
    Task RollbackTransactionAsync(CancellationToken cancellationToken = default);
    void RegisterAggregate(IAggregateRoot aggregate);
}
IBusinessRule
public interface IBusinessRule
{
    bool IsBroken();
    string Message { get; }
    string ErrorCode { get; }
}
ISpecification<T>
public interface ISpecification<T>
{
    bool IsSatisfiedBy(T candidate);
    Expression<Func<T, bool>> ToExpression();
    ISpecification<T> And(ISpecification<T> other);
    ISpecification<T> Or(ISpecification<T> other);
    ISpecification<T> Not();
}

Enhanced Configuration Interfaces

IDomainConfigurationBuilder
public interface IDomainConfigurationBuilder
{
    IFSEntityFrameworkBuilder Builder { get; }
    IDomainConfigurationBuilder WithAutoRepositoryDiscovery();
    IDomainConfigurationBuilder WithAutoRepositoryDiscovery(Assembly assembly);
    IDomainConfigurationBuilder WithRepository<TAggregate, TKey>() where TAggregate : AggregateRoot<TKey> where TKey : IEquatable<TKey>;
    IDomainConfigurationBuilder WithCustomRepository<TAggregate, TKey, TRepository>() where TRepository : class, IDomainRepository<TAggregate, TKey>;
    IDomainConfigurationBuilder WithDomainValidation();
    IFSEntityFrameworkBuilder Complete();
}

Domain Extension Methods

// βœ… DomainGuard Extensions
public static class DomainGuard
{
    public static void Against(IBusinessRule rule);
    public static void Against(bool condition, string message, string? errorCode = null);
    public static void AgainstNull<T>(T? value, string parameterName) where T : class;
    public static void AgainstNullOrWhiteSpace(string? value, string parameterName);
    public static void AgainstNegative(decimal value, string parameterName);
    public static void AgainstNegativeOrZero(decimal value, string parameterName);
    public static void AgainstOutOfRange<T>(T value, T min, T max, string parameterName) where T : IComparable<T>;
}

🀝 Contributing

We welcome contributions! This project is open source and benefits from community involvement.

Areas for Contribution - DDD Edition

  • πŸ›οΈ Enhanced DDD patterns (Saga patterns, Event Sourcing support)
  • πŸ”Œ Additional domain event dispatchers (Mass Transit, NServiceBus, etc.)
  • ⚑ Performance optimizations for aggregate loading and persistence
  • πŸ“‹ Advanced specification implementations
  • πŸ“š DDD documentation and examples
  • πŸ§ͺ DDD test coverage improvements
  • πŸ”‘ New ID generation strategies
  • 🎯 Domain modeling tools and utilities

Code Style

  • Use meaningful domain language in code
  • Follow DDD naming conventions
  • Add XML documentation for public APIs
  • Include unit tests for domain logic
  • Follow SOLID principles and DDD patterns

πŸ“„ License

This project is licensed under the MIT License. See the LICENSE file for details.


🌟 Acknowledgments

  • Thanks to all contributors who have helped make this library better
  • Inspired by Domain-Driven Design principles by Eric Evans
  • Built on top of the excellent Entity Framework Core
  • Special thanks to the .NET community for continuous feedback and support

πŸ“ž Support

If you encounter any issues or have questions:

  1. Check the troubleshooting section
  2. Search existing GitHub issues
  3. Create a new issue with detailed information
  4. Join our community discussions

Happy Domain Modeling! πŸ›οΈ


Made with ❀️ by Furkan Sarıkaya

GitHub LinkedIn Medium

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
9.0.7 308 7/20/2025
9.0.6.9 143 7/6/2025
9.0.6.8 135 7/6/2025
9.0.6.7 135 7/6/2025

GUID Version 7 Extension v9.0.7 - Enterprise DDD Implementation

           🆕 DOMAIN-DRIVEN DESIGN ENHANCEMENTS:
           - 🏛️ Full DDD compatibility with AggregateRoot<Guid> entities
           - 🎯 Seamless integration with Domain Events and Value Objects
           - 📋 Business Rules and Domain Specifications support
           - 🔄 Domain Unit of Work automatic ID generation
           - 🏗️ Enterprise-grade aggregate identifier strategy

           🚀 GUID V7 FEATURES:
           - 🆕 RFC 9562 compliant implementation using .NET 9 native support
           - 🆕 Automatic GUID Version 7 generation for BaseEntity<Guid> entities
           - 🆕 Fluent configuration: WithGuidV7() extension method
           - 🆕 Custom timestamp provider support for deterministic generation
           - 🆕 Zero external dependencies - uses built-in .NET 9 Guid.CreateVersion7()

           🏭 ENTERPRISE PRODUCTION FEATURES:
           - βœ… Optimal database performance with timestamp-based sequential ordering
           - βœ… Universal GUID ecosystem compatibility across microservices
           - βœ… Perfect for event-driven architectures and domain event ordering
           - βœ… Microsoft native implementation reliability and performance
           - βœ… Industry standard RFC compliance for enterprise environments

           💡 DDD INTEGRATION EXAMPLES:
           ```csharp
           // Automatic GUID V7 for Aggregate Roots
           public class ProductAggregate : AggregateRoot<Guid> { }

           // Fluent configuration with DDD
           services.AddFSEntityFramework<DbContext>()
           .WithGuidV7()
           .WithDomainDrivenDesign()
           .WithAutoRepositoryDiscovery()
           .Complete()
           .Build();
           ```

           🎯 PERFECT FOR:
           - Enterprise Domain-Rich Applications
           - Event-Driven Microservice Architectures
           - Multi-tenant SaaS Platforms
           - Financial and Healthcare Systems
           - High-throughput Transaction Systems

           REQUIREMENTS:
           - .NET 9+ for native Guid.CreateVersion7() support
           - Compatible with FS.EntityFramework.Library v9.0.7+