Core abstractions and interfaces for Entity Framework Core integration that establish the foundation for Domain-Driven Design (DDD) patterns and provide essential contracts for entity management, auditing, and domain events.
DKNet.EfCore.Abstractions defines the fundamental contracts and base classes for Entity Framework Core integration within the DKNet framework. It provides essential abstractions for:
This project serves as the cornerstone of the data access layer, ensuring consistent patterns across all EF Core implementations in the DKNet ecosystem.
DKNet.EfCore.Abstractions provides the Domain Layer with essential building blocks while maintaining technology independence:
┌─────────────────────────────────────────────────────────────────┐
│ 🌐 Presentation Layer │
│ │
│ No direct dependencies on EfCore.Abstractions │
└─────────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────────┴───────────────────────────────────────┐
│ 🎯 Application Layer │
│ │
│ May use: Repository interfaces, Domain event contracts │
└─────────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────────┴───────────────────────────────────────┐
│ 💼 Domain Layer │
│ │
│ 📋 Entity<TKey> - Domain entity base classes │
│ 🎭 IEventEntity - Domain event capabilities │
│ 📝 AuditedEntity - Audit trail for business entities │
│ 🏷️ StaticDataAttribute - Reference data management │
│ 🔒 IConcurrencyEntity - Business rule consistency │
└─────────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────────┴───────────────────────────────────────┐
│ 🗄️ Infrastructure Layer │
│ │
│ Implements: Repository patterns, EF Core configurations │
│ Uses: All abstractions for concrete implementations │
└─────────────────────────────────────────────────────────────────┘
IEntity<TKey>
ensures every domain entity has a well-defined identityIConcurrencyEntity
supports optimistic concurrency for aggregate boundariesIEventEntity
enables domain entities to publish events for cross-aggregate communicationAuditedEntity
maintains comprehensive audit logs for compliance and business requirementsStaticDataAttribute
helps maintain reference data consistency across the domaindotnet add package DKNet.EfCore.Abstractions
using DKNet.EfCore.Abstractions.Entities;
// Domain entity with GUID primary key
public class Product : Entity
{
public string Name { get; private set; } = null!;
public decimal Price { get; private set; }
public string Description { get; private set; } = string.Empty;
// Private constructor for EF Core
private Product() { }
// Factory method for creating new products
public static Product Create(string name, decimal price, string description = "")
{
var product = new Product
{
Name = name,
Price = price,
Description = description
};
return product;
}
// Business logic methods
public void UpdatePrice(decimal newPrice)
{
if (newPrice <= 0)
throw new ArgumentException("Price must be positive", nameof(newPrice));
Price = newPrice;
}
}
using DKNet.EfCore.Abstractions.Entities;
// Entity with integer primary key
public class Category : Entity<int>
{
public string Name { get; private set; } = null!;
public string Code { get; private set; } = null!;
private Category() { }
public Category(int id, string name, string code) : base(id)
{
Name = name;
Code = code;
}
}
using DKNet.EfCore.Abstractions.Entities;
// Entity with full audit tracking
public class Order : AuditedEntity, IEventEntity
{
private readonly List<object> _domainEvents = new();
private readonly List<Type> _domainEventTypes = new();
public string OrderNumber { get; private set; } = null!;
public Guid CustomerId { get; private set; }
public OrderStatus Status { get; private set; }
public decimal TotalAmount { get; private set; }
private Order() { }
public static Order Create(string orderNumber, Guid customerId, string createdBy)
{
var order = new Order
{
OrderNumber = orderNumber,
CustomerId = customerId,
Status = OrderStatus.Pending,
TotalAmount = 0
};
// Set audit information
order.SetCreatedBy(createdBy);
// Raise domain event
order.AddEvent(new OrderCreatedEvent(order.Id, customerId, orderNumber));
return order;
}
public void CompleteOrder(string modifiedBy)
{
if (Status != OrderStatus.Processing)
throw new InvalidOperationException("Only processing orders can be completed");
Status = OrderStatus.Completed;
SetUpdatedBy(modifiedBy);
// Raise domain event
AddEvent<OrderCompletedEvent>();
}
// IEventEntity implementation
public void AddEvent(object eventObj)
{
_domainEvents.Add(eventObj);
}
public void AddEvent<TEvent>() where TEvent : class
{
_domainEventTypes.Add(typeof(TEvent));
}
public (object[]? events, Type[]? eventTypes) GetEventsAndClear()
{
var events = _domainEvents.ToArray();
var eventTypes = _domainEventTypes.ToArray();
_domainEvents.Clear();
_domainEventTypes.Clear();
return (events.Length > 0 ? events : null,
eventTypes.Length > 0 ? eventTypes : null);
}
}
public enum OrderStatus
{
Pending = 1,
Processing = 2,
Completed = 3,
Cancelled = 4
}
using DKNet.EfCore.Abstractions.Attributes;
using System.ComponentModel.DataAnnotations;
// Enum that will be stored as a reference table
[StaticData(nameof(OrderStatus))]
public enum OrderStatus
{
[Display(Name = "Pending", Description = "Order is waiting for processing")]
Pending = 1,
[Display(Name = "Processing", Description = "Order is being processed")]
Processing = 2,
[Display(Name = "Completed", Description = "Order has been completed")]
Completed = 3,
[Display(Name = "Cancelled", Description = "Order has been cancelled")]
Cancelled = 4
}
using DKNet.EfCore.Abstractions.Entities;
// Repository interface in the domain layer
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(Guid id);
Task<Order?> GetByOrderNumberAsync(string orderNumber);
Task<IEnumerable<Order>> GetOrdersByCustomerAsync(Guid customerId);
Task<Order> AddAsync(Order order);
Task UpdateAsync(Order order);
Task DeleteAsync(Order order);
}
using DKNet.EfCore.Abstractions.Entities;
// Aggregate root
public class Customer : AuditedEntity, IEventEntity
{
private readonly List<Address> _addresses = new();
private readonly List<object> _domainEvents = new();
private readonly List<Type> _domainEventTypes = new();
public string FirstName { get; private set; } = null!;
public string LastName { get; private set; } = null!;
public string Email { get; private set; } = null!;
// Read-only collection of child entities
public IReadOnlyCollection<Address> Addresses => _addresses.AsReadOnly();
private Customer() { }
public static Customer Create(string firstName, string lastName, string email, string createdBy)
{
var customer = new Customer
{
FirstName = firstName,
LastName = lastName,
Email = email
};
customer.SetCreatedBy(createdBy);
customer.AddEvent(new CustomerCreatedEvent(customer.Id, email));
return customer;
}
// Business method that maintains aggregate consistency
public void AddAddress(string street, string city, string postalCode, string modifiedBy)
{
var address = new Address(Id, street, city, postalCode);
_addresses.Add(address);
SetUpdatedBy(modifiedBy);
AddEvent(new CustomerAddressAddedEvent(Id, address.Id));
}
// IEventEntity implementation
public void AddEvent(object eventObj) => _domainEvents.Add(eventObj);
public void AddEvent<TEvent>() where TEvent : class => _domainEventTypes.Add(typeof(TEvent));
public (object[]? events, Type[]? eventTypes) GetEventsAndClear()
{
var events = _domainEvents.ToArray();
var eventTypes = _domainEventTypes.ToArray();
_domainEvents.Clear();
_domainEventTypes.Clear();
return (events.Length > 0 ? events : null, eventTypes.Length > 0 ? eventTypes : null);
}
}
// Child entity (part of Customer aggregate)
public class Address : Entity
{
public Guid CustomerId { get; private set; }
public string Street { get; private set; } = null!;
public string City { get; private set; } = null!;
public string PostalCode { get; private set; } = null!;
private Address() { }
internal Address(Guid customerId, string street, string city, string postalCode)
{
CustomerId = customerId;
Street = street;
City = city;
PostalCode = postalCode;
}
}
using DKNet.EfCore.Abstractions.Attributes;
// Entity that should be ignored by automatic mapping
[IgnoreEntityMapper]
public class TemporaryData : Entity
{
public string Data { get; set; } = null!;
}
// Entity with custom sequence configuration
public class Invoice : Entity<long>
{
[SqlSequence("InvoiceNumberSequence")]
public long InvoiceNumber { get; private set; }
public string CustomerName { get; private set; } = null!;
public decimal Amount { get; private set; }
}
// Domain event classes
public record CustomerCreatedEvent(Guid CustomerId, string Email);
public record CustomerAddressAddedEvent(Guid CustomerId, Guid AddressId);
public record OrderCreatedEvent(Guid OrderId, Guid CustomerId, string OrderNumber);
public record OrderCompletedEvent(Guid OrderId, Guid CustomerId, DateTime CompletedAt);
// Event handler interface (would be implemented in application layer)
public interface IEventHandler<in TEvent>
{
Task Handle(TEvent domainEvent);
}
using Microsoft.EntityFrameworkCore;
using DKNet.EfCore.Abstractions.Entities;
public class ApplicationDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; } = null!;
public DbSet<Order> Orders { get; set; } = null!;
public DbSet<Product> Products { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure entities using the abstractions
modelBuilder.Entity<Customer>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.RowVersion).IsRowVersion();
// Configure audit properties
entity.Property(e => e.CreatedBy).IsRequired();
entity.Property(e => e.CreatedOn).IsRequired();
// Configure child entities
entity.OwnsMany(e => e.Addresses, address =>
{
address.WithOwner().HasForeignKey(a => a.CustomerId);
address.HasKey(a => a.Id);
});
});
// Configure static data enums
modelBuilder.Entity<OrderStatusEntity>().HasData(
GetStaticDataFromEnum<OrderStatus>()
);
base.OnModelCreating(modelBuilder);
}
private static IEnumerable<object> GetStaticDataFromEnum<TEnum>() where TEnum : struct, Enum
{
return Enum.GetValues<TEnum>()
.Select(e => new { Id = (int)(object)e, Name = e.ToString() });
}
}
AuditedEntity
for entities that require compliance trackingIConcurrencyEntity
for entities that may be modified concurrentlyStaticDataAttribute
for enums that represent reference dataDKNet.EfCore.Abstractions integrates seamlessly with other DKNet components:
💡 Architecture Tip: Use DKNet.EfCore.Abstractions to define your domain entities and let the infrastructure layer handle the EF Core specifics. This maintains clean separation between business logic and data access technology.