WMSSeptember 2026 · 4 min read

Building Inventory Management with EF Core

By go2ismail · Published · .NET 10

At a glance

EF Core maps the warehouse model and executes stock queries, while business handlers calculate movements. AppDbContext adds audit, soft deletion, and asynchronous auto-number behavior.

Start with the existing inventory model

Building inventory management with EF Core involves more than exposing DbSet properties. You need a model for movements, queries that match operational status, and a save path that preserves the application’s shared behavior. The Blazor WMS Source Code source supplies a concrete example using EF Core 10 and an AppDbContext derived from IdentityDbContext<ApplicationUser>.

Its DbSets include Product, Warehouse, InventoryTransaction, purchase and sales documents, and other application entities. The context also depends on IServiceScopeFactory and ICurrentUserService. A minimal tutorial context accepting only DbContextOptions would therefore not be a drop-in replacement for this implementation.

Map navigation properties explicitly

InventoryTransaction refers to Warehouse three times: the stock warehouse, the movement origin, and the movement destination. The source configuration explicitly identifies each foreign key and uses NoAction delete behavior. For example:

// Excerpt from InventoryTransactionConfiguration
builder.HasOne(e => e.WarehouseFrom)
    .WithMany()
    .HasForeignKey(e => e.WarehouseFromId)
    .OnDelete(DeleteBehavior.NoAction);

AppDbContext applies entity configurations from its assembly. When adding a relationship, inspect both the entity and configuration class so that multiple references to the same parent remain unambiguous. The database design guide diagrams the relevant foreign-key properties.

Query balances from confirmed signed contributions

The stock-report handler filters confirmed inventory transactions for physical products and non-system warehouses, groups by WarehouseId and ProductId, and sums Stock. The following teaching query applies those same filters to a single warehouse-product balance. It assumes an injected AppDbContext, existing identifier variables, the inventory enum namespace, and Microsoft.EntityFrameworkCore.

// Teaching query adapted from the confirmed-stock rule
var quantity = await context.InventoryTransaction
    .Where(x => x.WarehouseId == warehouseId
        && x.ProductId == productId
        && x.Product!.Physical == true
        && x.Warehouse!.SystemWarehouse == false
        && x.Status == InventoryTransactionStatus.Confirmed)
    .SumAsync(x => x.Stock ?? 0.0, cancellationToken);

Stock is already signed by the calculation helper. Multiplying by the direction again in this query would apply the sign twice to outbound rows. The global soft-delete filter also participates in ordinary context queries. Read the complete report handler when adding date filters, projections, or additional grouping dimensions.

Use projection for list responses

GetWarehouseListHandler uses AsNoTracking, orders by Name, and projects into GetWarehouseListResponse before ToListAsync. It returns the fields needed by the feature rather than handing a tracked entity graph directly to the caller. This is a concrete read path to follow when adding another maintenance list.

The inspected warehouse list retrieves the matching list without server-side pagination. If record volume makes that unsuitable, add a deliberate query contract and update the client together. A pagination type elsewhere in the codebase does not automatically make every list operation paginated.

Save through the application’s lifecycle

The asynchronous SaveChanges override detects changes, applies soft deletion, applies audit data, generates missing automatic numbers for participating new entities, and then calls the EF Core base implementation. The synchronous override applies soft deletion and audit, but does not call the same asynchronous numbering routine. Preserve the asynchronous save path when extending handlers that rely on it.

// Excerpt from AppDbContext.SaveChangesAsync
ChangeTracker.DetectChanges();
ApplySoftDelete();
ApplyAudit(_currentUserService.UserId ?? string.Empty);
await ApplyAutoNumber(cancellationToken);
return await base.SaveChangesAsync(cancellationToken);

ApplySoftDelete changes deleted entries implementing IHasIsDeleted into modified entries with IsDeleted set to true. ApplyAudit sets timestamps and actor identifiers, while preserving the original creation fields on updates. These mechanisms are part of this context’s save behavior; alternative bulk or direct SQL paths require separate review.

Understand what the query filter guarantees

The context builds an IsDeleted == false filter for participating entities. It hides soft-deleted rows from ordinary filtered queries; it does not physically remove them. Microsoft’s global query filter documentation explains this pattern and the explicit APIs that can disable filters. Review reporting requirements before bypassing the filter.

Separate initial creation from database upgrades

The inspected Program.cs uses EnsureCreated and DatabaseSeeder for selected relational providers. EnsureCreated is not a migration process for evolving an existing database. Microsoft’s schema creation documentation distinguishes it from migrations. A data-preserving upgrade needs a reviewed transition strategy; do not assume restarting the app adds a new column.

For development, validate a model change against the intended database provider and inspect its generated schema. The source references several relational providers, but cross-provider compatibility of a particular customization must be established by its own checks. No production database was modified to write this guide.

Verify persistence through business outcomes

A focused check creates and reloads a warehouse, confirms that audit fields are populated, soft-deletes an eligible record, and verifies its normal list visibility. For inventory, verify receipt and delivery contributions and confirm that draft rows are excluded. Simultaneous stock writes, retry behavior, and retained-data schema upgrades need their own scenarios; a working EF query does not prove those properties.

Continue to the field-level inventory schema for model details or the WMS source-code product for the complete application implementation.