Entity Framework Extensions Primary Key Options with Entity Framework Extensions
The ColumnPrimaryKeyExpression
and ColumnPrimaryKeyNames
options allow you to perform bulk operations with a custom key.
Instead of using the primary key mapped in your entities, these options use the key you provide.
A custom key is often required in these two common scenarios:
- External Key: You need to save data but only know the external key from another system.
- Different Key: You need to save data but only know the customer
code
instead of theid
.
These situations often occur when importing data.
Using ColumnPrimaryKeyExpression
You can use ColumnPrimaryKeyExpression
to specify the property (or properties) that will be used as the key:
- Simple lambda expression — can only be used when mapping one property
- Lambda with a body — can be used to map one or more properties
// @nuget: Z.EntityFramework.Extensions.EFCore using Z.EntityFramework.Extensions; // Single property (simple lambda) context.BulkUpdate(list, options => { options.ColumnPrimaryKeyExpression = x => x.ID; }); // Multiple properties (lambda with a body) context.BulkUpdate(list, options => { options.ColumnPrimaryKeyExpression = x => new { x.ID, x.ID2 }; });
Using ColumnPrimaryKeyNames
You can use ColumnPrimaryKeyNames
to specify the key properties by providing a list of strings:
// @nuget: Z.EntityFramework.Extensions.EFCore using Z.EntityFramework.Extensions; // Single property from an external list (dynamic source) var externalKeyList = new List<string> { "ID" }; context.BulkUpdate(list, options => { options.ColumnPrimaryKeyNames = externalKeyList; }); // Multiple properties using nameof() context.BulkUpdate(list, options => { options.ColumnPrimaryKeyNames = new List<string> { nameof(MyEntity.ID), nameof(MyEntity.ID2) }; });
You can learn more about options configuration and the difference between the Expression
and Names
suffix in these articles:
When Your EF Core Primary Key Isn’t Enough — How to Handle Custom Keys
An entity to be saved in EF Core always requires a key.
There are multiple ways to map the key, such as:
- Convention-based configuration (naming a property
ID
or<EntityName>ID
) - Mapping a key with Data Annotations
- Mapping a key with Fluent API
A key can be:
- A single key (one property)
- A composite key (multiple properties)
Everything works fine until you hit one of these two common scenarios:
- External Key: You need to save data but only know the external key from another system.
- Different Key: You need to save data but only know the customer
code
instead of theid
.
So what’s the solution when this happens?
- WhereBulkContains — Allows you to retrieve entities using a custom key, and then copy properties to those retrieved entities.
- SyncPropertiesFromDatabaseValues — Allows you to synchronize the key property from a custom key, then attach those entities and save them.
Both solutions work fine — but there’s an even better one: using a custom key directly!
You can do this with any of our bulk operations by using the ColumnPrimaryKeyExpression
or ColumnPrimaryKeyNames
options.
How to Customize the Primary Key Even More
Sometimes you need to add extra logic to the primary key to make it even more specific — for example, ensuring you do not update rows that are “locked.”
For these special scenarios, we offer four additional methods:
- InsertPrimaryKeyAndFormula — Allows you to customize the logic after the primary key for the BulkInsert method. Requires the use of the
InsertIfNotExists
option. - UpdatePrimaryKeyAndFormula — Allows you to customize the logic after the primary key for the BulkUpdate method.
- DeletePrimaryKeyAndFormula — Allows you to customize the logic after the primary key for the BulkDelete method.
- MergePrimaryKeyAndFormula — Allows you to customize the logic after the primary key for the BulkMerge method.
⚠️ Be careful with BulkMerge
— if entities don’t match the condition, they will be inserted instead.
In most cases, you’ll want to use the MergeMatchedAndFormula
option instead, which skips the update for matched entities if the condition is not satisfied.
// @nuget: Z.EntityFramework.Extensions.EFCore using Z.EntityFramework.Extensions; // Use a formula to avoid updating "locked" rows context.BulkUpdate(list, options => { // Note: UpdateMatchedAndFormula would be a better choice for this scenario options.UpdatePrimaryKeyAndFormula = "NOT DestinationTable.IsLocked"; });
Choosing Between Expression, Names, and PrimaryKeyAndFormula
Option | Best For | Pros | Cons |
---|---|---|---|
ColumnPrimaryKeyExpression |
When you know the property names at compile time | Type-safe (compile-time checking), easier refactoring, IntelliSense support | Requires recompilation if the key changes |
ColumnPrimaryKeyNames |
When the key properties come from a dynamic source | Flexible for runtime/dynamic scenarios | No compile-time checking, risk of typos in property names |
PrimaryKeyAndFormula |
When you need to add extra conditions to the matching logic | Handles advanced scenarios (e.g., skip “locked” rows) | More complex, requires SQL syntax, potential performance overhead |
Conclusion
Handling primary keys in EF Core is straightforward — until you face real-world scenarios like importing data from another system or working with a different unique identifier.
With ColumnPrimaryKeyExpression
and ColumnPrimaryKeyNames
, you can easily define a custom key for your bulk operations, avoiding the need for extra lookups or complex workarounds.
And when you need even more control, the PrimaryKeyAndFormula options let you add custom matching logic, giving you the flexibility to handle edge cases like “locked” rows or conditional updates.
By choosing the right approach for your scenario, you can keep your bulk operations fast, accurate, and tailored to your data requirements.
ZZZ Projects