c# - How can I set the value of a shadow property to the value of another property on insert in EF Core? - Stack Overflow

admin2025-05-02  1

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()");
    
    ...
}
Share Improve this question edited Jan 3 at 9:14 Gert Arnold 109k36 gold badges214 silver badges313 bronze badges asked Jan 2 at 13:22 ShuzhengShuzheng 14.2k29 gold badges121 silver badges234 bronze badges 2
  • Why do you allow the user to set an UpdatedAt, but not the CreatedAt. it seems like both are "audit" columns that should be handled by the system and not by users. Have you looked at interceptors to achieve what you are trying to do? – Fran Commented Jan 3 at 1:47
  • What is this NameLength example for? I think it's only confusing, so it's better to remove it. Also EF.Property() is always read-only, so you can skip that part too. – Gert Arnold Commented Jan 3 at 9:14
Add a comment  | 

1 Answer 1

Reset to default 1

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.

转载请注明原文地址:http://www.anycun.com/QandA/1746117880a91915.html