Course contentsShow
← C#
Lesson 1164 of 1,90810. Entity Framework Core ModelingFree lesson

Data Integrity Validation

Apply cross-entity validation rules before calling SaveChanges to ensure consistency.

In this lesson, you will learn how to enforce cross-entity consistency by implementing validation logic before committing changes to the database.

The Challenge of Cross-Entity Integrity

While data annotations provide simple validation for individual properties, they cannot easily verify relationships or dependencies between separate entities. For example, if you are building an e-commerce system, you might need to ensure that a DiscountCode is not applied to an Order if the order's total weight exceeds a specific threshold. Because these two pieces of data exist on different entities, relying on simple per-property validation is insufficient.

To maintain integrity, we must validate these rules after the change tracking has captured the modifications but before the SQL commands are executed in the database.

Implementing Validation in SaveChanges

The most robust way to handle this is by overriding SaveChangesAsync in your DbContext. By intercepting the process here, you ensure that no invalid state ever reaches the persistence layer.

public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
    // Access the ChangeTracker to inspect entities pending changes
    var entries = ChangeTracker.Entries<Order>();

    foreach (var entry in entries)
    {
        if (entry.State == EntityState.Added || entry.State == EntityState.Modified)
        {
            // Perform cross-entity check: 
            // Ensure the shipping method is compatible with the order region
            var region = await Regions.FindAsync(entry.Entity.RegionId);
            if (!entry.Entity.ShippingMethod.IsSupportedIn(region.Code))
            {
                throw new InvalidOperationException("Shipping method mismatch.");
            }
        }
    }

    return await base.SaveChangesAsync(ct);
}

By performing this check inside the DbContext, you create a "safety gate." If the validation logic fails, the exception prevents base.SaveChangesAsync() from running, ensuring the database remains in a valid, consistent state. This pattern is particularly useful for complex business rules that depend on data currently held in the database (like checking stock levels or budget limits) that are not immediately available in the entity object itself.

Key Takeaway: Overriding SaveChangesAsync allows you to enforce complex, cross-entity business rules that act as a final barrier against inconsistent data before persistence.