How can I set the value of a shadow property to the value of another property on first insert?
I want to record the time when a entity was inserted using the CreatedAt
shadow property, which is created as such, because it should be opaque to the user.
While I configure it would a default value (HasDefaultValueSql("GETUTCDATE()")
), I would like it to have it match the user-provided UpdatedAt
value on insert.
Furthermore, is it possible to have it readonly there, so that it's not possible to change its value using e.g. EF.Property<DateTime>(x, "CreatedAt") = DateTime.UtcNow
?
I have considered the stored version of HasComputedColumnSql
, but it would require a SQL conditional operator, like [CreatedAt] ? [CreatedAt] : [UpdatedAt]
, which I don't think exist.
modelBuilder.Entity<Person>()
.Property(p => p.NameLength)
.HasComputedColumnSql("LEN([LastName]) + LEN([FirstName])", stored: true);
#computed-columns
Notification Entity:
public sealed record Notification(
AssetType AssetType,
Guid AssetId,
AssetIssue AssetIssue,
Guid OwnerId,
string DisplayName,
string? GivenName,
string? Surname,
string UserPrincipalName)
{
public required NotificationStatus Status { get; set; }
public required int NotificationCount { get; set; }
public required DateTime UpdatedAt { get; set; }
}
IEntityTypeConfiguration:
public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<Notification> builder)
{
// Properties
...
builder.Property(p => p.UpdatedAt).HasDefaultValueSql("GETUTCDATE()");
// Shadow Properties
builder.Property<DateTime>("CreatedAt").HasDefaultValueSql("GETUTCDATE()");
...
}
How can I set the value of a shadow property to the value of another property on first insert?
I want to record the time when a entity was inserted using the CreatedAt
shadow property, which is created as such, because it should be opaque to the user.
While I configure it would a default value (HasDefaultValueSql("GETUTCDATE()")
), I would like it to have it match the user-provided UpdatedAt
value on insert.
Furthermore, is it possible to have it readonly there, so that it's not possible to change its value using e.g. EF.Property<DateTime>(x, "CreatedAt") = DateTime.UtcNow
?
I have considered the stored version of HasComputedColumnSql
, but it would require a SQL conditional operator, like [CreatedAt] ? [CreatedAt] : [UpdatedAt]
, which I don't think exist.
modelBuilder.Entity<Person>()
.Property(p => p.NameLength)
.HasComputedColumnSql("LEN([LastName]) + LEN([FirstName])", stored: true);
https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations#computed-columns
Notification Entity:
public sealed record Notification(
AssetType AssetType,
Guid AssetId,
AssetIssue AssetIssue,
Guid OwnerId,
string DisplayName,
string? GivenName,
string? Surname,
string UserPrincipalName)
{
public required NotificationStatus Status { get; set; }
public required int NotificationCount { get; set; }
public required DateTime UpdatedAt { get; set; }
}
IEntityTypeConfiguration:
public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<Notification> builder)
{
// Properties
...
builder.Property(p => p.UpdatedAt).HasDefaultValueSql("GETUTCDATE()");
// Shadow Properties
builder.Property<DateTime>("CreatedAt").HasDefaultValueSql("GETUTCDATE()");
...
}
In EF Core, you cannot directly set a value for a shadow property using EF.Property
unless you use ExecuteUpdate
or ExecuteUpdateAsync
.
Shadow property values are managed within the ChangeTracker
, and updates should be performed through an entity's Entry
. Here's an example:
var modifiedEntries = context.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added || entry.State == EntityState.Modified);
foreach (var entry in modifiedEntries)
{
var propertyEntry = entry.Property<DateTime>("CreatedAt");
if (propertyEntry != null && entry.State == EntityState.Added)
{
propertyEntry.CurrentValue = DateTime.Now;
}
}
This approach can also be applied to manage other shadow properties.
NameLength
example for? I think it's only confusing, so it's better to remove it. AlsoEF.Property()
is always read-only, so you can skip that part too. – Gert Arnold Commented Jan 3 at 9:14