EFCore.DataClassification
1.1.0
dotnet add package EFCore.DataClassification --version 1.1.0
NuGet\Install-Package EFCore.DataClassification -Version 1.1.0
<PackageReference Include="EFCore.DataClassification" Version="1.1.0" />
<PackageVersion Include="EFCore.DataClassification" Version="1.1.0" />
<PackageReference Include="EFCore.DataClassification" />
paket add EFCore.DataClassification --version 1.1.0
#r "nuget: EFCore.DataClassification, 1.1.0"
#:package EFCore.DataClassification@1.1.0
#addin nuget:?package=EFCore.DataClassification&version=1.1.0
#tool nuget:?package=EFCore.DataClassification&version=1.1.0
EFCore.DataClassification
Overview
EFCore.DataClassification is a small extension library for Entity Framework Core 8 (EF Core 8) that adds SQL Server data classification support on top of the standard migrations pipeline.
It lets you:
- Annotate properties in your entity classes with a
[DataClassification]attribute. - Or configure classification via Fluent API (
HasDataClassification). - Automatically generate SQL Server metadata for:
ADD SENSITIVITY CLASSIFICATION(native SQL Server feature),- and
sp_addextendedproperty/sp_dropextendedpropertycalls for label, information type and rank.
- Keep the classification metadata in sync with EF Core migrations (add, remove, or change columns → classification migrations are generated accordingly).
- Validate rank values and label lengths at migration time with clear error messages.
The solution also includes:
EFCore.DataClassification.Tests– unit and integration tests for the library.EFCore.DataClassification.WebApi– a minimal ASP.NET Core Web API sample that demonstrates how to use the library in a real application.
Projects
EFCore.DataClassification- Core library.
- Contains attributes, annotations, extensions, SQL generator, custom migration operations and model differ.
EFCore.DataClassification.Tests- xUnit tests for attributes, extensions, SQL generator, and migration model differ.
EFCore.DataClassification.WebApi- Example ASP.NET Core 8 Web API application.
- Uses SQL Server + this library to demonstrate classification on a
Userand other sample entities.
Requirements
- .NET 8.0
- SQL Server 2017+:
- SQL Server 2019 & Azure SQL: Full support (Native Sensitivity Classification + Extended Properties).
- SQL Server 2017: Partial support (Extended Properties only). The library automatically detects the version and skips unsupported commands safely.
- Entity Framework Core 8 (
Microsoft.EntityFrameworkCore.SqlServer8.0.22)
Core Concepts
DataClassification constants and ranks
The library defines a central set of constants and valid rank values in DataClassificationConstants:
- Annotations (EF Core metadata keys):
DataClassification:LabelDataClassification:InformationTypeDataClassification:Rank
- Max lengths:
MaxLabelLength = 128MaxInformationTypeLength = 128
- Default schema:
dbo - Allowed ranks (maps to SQL Server ranks):
None,Low,Medium,High,Critical
It also exposes:
IsValidRank(string? rank)– checks if a rank is allowed.GetAllowedRanksString()– returns allowed ranks as comma-separated string for error messages.
SensitivityRank enum
The SensitivityRank enum lives in the Models folder:
public enum SensitivityRank
{
None,
Low,
Medium,
High,
Critical
}
This is the rank you use in attributes and Fluent API.
How It Works (High-Level)
You mark entity properties with:
[DataClassification(...)]attribute, orHasDataClassification(...)Fluent API.
Model building:
ModelBuilder.UseDataClassification()scans all entity types and their properties.- For each property with
[DataClassification], it writes EF Core annotations usingDataClassificationConstants.Label,InformationType, andRank.
Migrations model differ:
- During migrations diffing,
DataClassificationMigrationsModelDifferlooks at column mappings and checks whether they have classification annotations. - It adds custom migration operations:
CreateDataClassificationOperationRemoveDataClassificationOperation
- It manages change detection and ordering:
- When a column is dropped, the classification remove operation is executed before the column drop.
- When a column is renamed, it removes classification from the old name and adds it to the new name.
- When a column is altered (type/nullability change), classification operations are ordered correctly.
- When classification is added, removed, or changed, appropriate operations are generated.
- Uses optimized sorting logic to ensure correct operation order for all scenarios.
- During migrations diffing,
SQL generation:
DataClassificationSqlGeneratorintercepts these custom operations and:- Writes extended properties with
sp_addextendedproperty/sp_dropextendedproperty(works on all SQL Server versions). - Writes SQL Server sensitivity classification using
ADD SENSITIVITY CLASSIFICATION ... WITH (LABEL = ..., INFORMATION_TYPE = ..., RANK = ...). - Automatically detects SQL Server version: Uses
SERVERPROPERTY('ProductMajorVersion')to check if the server supports sensitivity classification (SQL Server 2019+). On SQL Server 2017, only extended properties are written; sensitivity classification commands are safely skipped.
- Writes extended properties with
- It validates:
- Rank values (must be one of the allowed ranks).
- Label length (max 128 chars).
- On invalid configuration, it throws
DataClassificationExceptionwith a helpful message.
Design-time services:
DataClassificationDesignTimeServices(library) andDesign(WebApi) register:IMigrationsCodeGeneratorasDataClassificationMigrationsGenerator(adds needed namespaces to generated migrations).ICSharpMigrationOperationGeneratorasDataClassificationMigrationOperationGenerator(writes C# code for custom operations).
Installation & Setup
1. Add the library to your project
You can either:
- Reference the project directly from your solution:
- Add the existing
EFCore.DataClassificationproject to your solution. - Add a Project Reference to it from your application (Web, API, etc.).
- Add the existing
Or:
- (If packaged) add a NuGet package reference (not shown in this repo, but conceptually it would be something like
EFCore.DataClassification).
2. Configure DbContext options
In your application (for example in Program.cs):
builder.Services.AddDbContext<AppDbContext>(options =>
{
options
.UseSqlServer(connectionString)
.UseDataClassificationSqlServer(); // <-- enables library services
});
UseDataClassificationSqlServer():
- Registers
DataClassificationDbContextOptionsExtension. - That extension wires:
IMigrationsSqlGenerator→DataClassificationSqlGeneratorIMigrationsModelDiffer→DataClassificationMigrationsModelDiffer
3. Enable classification scanning in your DbContext
Inside your DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 1. Scan for [DataClassification] attributes
modelBuilder.UseDataClassification();
// 2. Optional Fluent API example
modelBuilder.Entity<User>()
.Property(u => u.PhoneNumber)
.HasDataClassification("Internal", "Phone Number", SensitivityRank.High);
}
Using the Attribute
DataClassificationAttribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class DataClassificationAttribute : Attribute
{
public string Label { get; }
public string InformationType { get; }
public SensitivityRank Rank { get; }
public DataClassificationAttribute(string label, string informationType, SensitivityRank rank)
{
Label = label;
InformationType = informationType;
Rank = rank;
}
}
Example – User entity
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Surname { get; set; } = string.Empty;
// Attribute-based classification
[DataClassification("Private", "Home Address", SensitivityRank.Medium)]
public string Adress { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
// Fluent API example is applied in OnModelCreating (PhoneNumber)
[DataClassification("Confidential", "Financial Information", SensitivityRank.High)]
public int Salary { get; set; }
[DataClassification("Confidential", "Admin Reference", SensitivityRank.High)]
public int? AdminId { get; set; }
public Admin? Admin { get; set; }
}
Another example – Customer:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
[DataClassification("Contact", "Email Address", SensitivityRank.High)]
public string Email { get; set; } = string.Empty;
[DataClassification("Address", "Mailing Address", SensitivityRank.None)]
public string Address { get; set; } = string.Empty;
}
After running migrations, these properties will have SQL Server sensitivity classification and extended properties attached.
Using Fluent API
You can also set classification via extensions on PropertyBuilder:
public static class PropertyBuilderExtensions
{
public static PropertyBuilder HasDataClassification(
this PropertyBuilder propertyBuilder,
string label,
string informationType,
SensitivityRank rank)
{ ... }
public static PropertyBuilder<TProperty> HasDataClassification<TProperty>(
this PropertyBuilder<TProperty> propertyBuilder,
string label,
string informationType,
SensitivityRank rank)
{ ... }
}
Example – from AppDbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.UseDataClassification();
modelBuilder.Entity<User>()
.Property(u => u.PhoneNumber)
.HasDataClassification("Internal", "Phone Number", SensitivityRank.High);
}
Migrations Support
Custom operations
The library introduces two custom migration operations:
CreateDataClassificationOperation- Used when a column with classification is added or when classification is changed.
RemoveDataClassificationOperation- Used when classification should be removed (for example when a column is dropped, unmapped, or its classification is removed/changed).
MigrationBuilder extensions
You can also explicitly add/remove classification in your migration code using MigrationBuilderExtensions:
public static class MigrationBuilderExtensions
{
public static OperationBuilder<CreateDataClassificationOperation> AddDataClassification(
this MigrationBuilder migrationBuilder,
string table,
string column,
string? schema = null,
string? label = null,
string? informationType = null,
string? rank = null);
public static OperationBuilder<RemoveDataClassificationOperation> DropDataClassification(
this MigrationBuilder migrationBuilder,
string table,
string column,
string? schema = null);
}
This is helpful if you want manual control over classification operations inside a particular migration.
SQL generation details
DataClassificationSqlGenerator:
- Validates incoming data:
- Allowed ranks (
None,Low,Medium,High,Critical). - Label length (
<= 128).
- Allowed ranks (
- Writes extended properties:
- Uses
sys.sp_addextendedpropertyandsys.sp_dropextendedproperty. - Properties:
DataClassification:LabelDataClassification:InformationTypeDataClassification:Rank
- Works on all SQL Server versions (2017+): Extended properties are always written regardless of SQL Server version.
- Uses
- Writes sensitivity classification:
- Uses
ADD SENSITIVITY CLASSIFICATION TO [schema].[table].[column] WITH (...). - Maps rank string values to SQL Server ranks:
"Low" → "LOW","Medium" → "MEDIUM","High" → "HIGH","Critical" → "CRITICAL".
"None"is treated as no sensitivity classification (no rank sent).- Version-aware execution: The generator automatically checks SQL Server version using
SERVERPROPERTY('ProductMajorVersion'):- SQL Server 2019+ (version 15+): Both extended properties AND native sensitivity classification are written.
- SQL Server 2017 (version 14): Only extended properties are written. Sensitivity classification commands are skipped safely (wrapped in
IF TRY_CONVERT(int, SERVERPROPERTY('ProductMajorVersion')) >= 15).
- This ensures migrations run successfully on both SQL Server 2017 and 2019+ without errors.
- Uses
If configuration is invalid, it throws DataClassificationException so you get a clear, early failure.
Web API Sample
The EFCore.DataClassification.WebApi project demonstrates an ASP.NET Core Web API using this library.
Key parts
Program- Configures
AppDbContextwith.UseDataClassificationSqlServer(). - Registers a global exception handler (
GlobalExceptionHandler). - Adds AutoMapper with
UserMappingProfile. - Enables Swagger/OpenAPI.
- Configures
AppDbContext- Defines multiple DbSets:
Users,Admins,Games,Car,Bikes,Homes,Customers,Documents, etc. - Calls
modelBuilder.UseDataClassification()inOnModelCreating. - Configures one property (
User.PhoneNumber) via Fluent APIHasDataClassification.
- Defines multiple DbSets:
Models
User,Customer,Admin,Game,Car,Bike,Home,Document, etc.- Several properties are decorated with
[DataClassification]to show different scenarios.
Controllers
UsersController:- Basic CRUD endpoints (
GET,POST,PUT,DELETE). - Additional queries (search, filter by admin).
- Uses DTOs and AutoMapper (
UserDtos,UserMappingProfile).
- Basic CRUD endpoints (
Middleware
GlobalExceptionHandler:- Implements
.NET 8 IExceptionHandlerpattern. - Maps:
DataClassificationException→ 400 Bad Request with specific error details.ArgumentNullException→ 400 Bad Request.InvalidOperationException→ 409 Conflict.- Any other exception → 500 Internal Server Error.
- Implements
Design-time
Designclass registers the design-time services for migrations generator and operation generator so thatdotnet efcommands produce migrations that understand the custom operations.
Running the Web API
- Configure a valid SQL Server connection string in
appsettings.json(e.g.DefaultConnection). - Run migrations, for example:
dotnet ef database update --project EFCore.DataClassification.WebApi - Start the API:
dotnet run --project EFCore.DataClassification.WebApi - Open Swagger UI (usually at
https://localhost:{port}/swagger) and test endpoints like:GET /api/usersPOST /api/usersGET /api/users/search?query=...
Tests
The EFCore.DataClassification.Tests project includes 55 comprehensive tests covering:
- Attributes – e.g.
DataClassificationAttributeTestsconfirms:- Properties set correctly.
- Attribute is applicable to properties only.
AllowMultiple = false.
- Extensions – tests for:
ModelBuilderExtensions.UseDataClassification()– annotations applied correctly.PropertyBuilderExtensions.HasDataClassification()– annotation-based configuration works.MigrationBuilderExtensions– custom operations are added correctly.
- Infrastructure:
DataClassificationMigrationsModelDiffer:- Adds
CreateDataClassificationOperationfor new classified columns. - Adds
RemoveDataClassificationOperationfor removed/changed classification. - Critical ordering tests (
CriticalOrderingTests): Bug-catcher style tests that verify:RemoveDataClassificationcomes beforeDropColumnoperations.RemoveDataClassificationcomes beforeRenameColumnoperations.CreateDataClassificationcomes afterRenameColumnoperations.RemoveDataClassificationcomes beforeAlterColumnoperations.CreateDataClassificationcomes afterAlterColumnoperations.
- Edge case tests (
DataClassificationMigrationsModelDifferEdgeCasesTests):- Columns without classification don't generate operations.
- Mixed scenarios (some classified, some not).
- Validation for label/information type length limits.
- Missing critical tests (
MissingCriticalTests):- Multiple column drops with correct ordering.
- Mixed operations (drop, rename, alter) with correct ordering.
- Custom schema handling.
- Schema equivalence (null vs "dbo").
- Adds
DataClassificationSqlGeneratorTests:- Generated SQL for extended properties and sensitivity classification.
- SQL Server version detection and conditional execution.
- Integration tests:
- Verify end-to-end behavior from model configuration to generated migrations and SQL.
- Test real-world scenarios with multiple models and complex schema changes.
Test Philosophy: The test suite emphasizes bug-catcher style tests that explicitly verify operation ordering and edge cases. These tests are designed to fail immediately if the sorting logic or operation generation is incorrect, ensuring reliability and maintainability.
To run tests:
dotnet test
Error Handling and Validation
When classification configuration is invalid, the library throws DataClassificationException:
- Used in
DataClassificationSqlGenerator.ValidateDataClassification(...). - Scenarios:
- Rank not in allowed set.
- Label too long.
In the Web API sample:
GlobalExceptionHandlercatchesDataClassificationExceptionand returns a 400 Bad Request with a clear error message.- In development, additional details may also be exposed for other exception types.
Code Quality & Performance
The library has been optimized for maintainability and performance:
- Optimized sorting logic: Refactored operation sorting to use a single pass with pattern matching, ensuring correct order for all column operations (drop, rename, alter).
- Conditional allocations: HashSet collections are only created when needed (e.g., when rename operations exist).
- Code deduplication: Helper methods (
AddCreateOperationIfNeeded,AddRemoveOperationIfNeeded) eliminate code duplication. - XML documentation: Key methods include comprehensive XML documentation for better IntelliSense support.
- Comprehensive test coverage: 55 tests including bug-catcher style tests that verify critical operation ordering.
Summary
EFCore.DataClassification is a focused EF Core 8 extension that:
- Adds an intuitive attribute + Fluent API for data classification.
- Integrates tightly with EF Core migrations and SQL Server sensitivity classification.
- Includes a ready-to-run Web API example and a comprehensive test suite (55 tests).
- Provides validation and clear error messages through
DataClassificationException. - Ensures correct operation ordering for all migration scenarios (add, remove, rename, alter columns).
- Optimized for performance and maintainability with clean, well-documented code.
License
This project is licensed under the MIT License - see the LICENSE file for details.
You are free to:
- ✅ Use commercially
- ✅ Modify
- ✅ Distribute
- ✅ Private use
- ✅ Sublicense
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. 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. |
-
net8.0
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.22)
- Microsoft.EntityFrameworkCore.SqlServer (>= 8.0.22)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.