Search code examples
c#entity-framework-coreentity-framework-core-3.0

EF Core save value from method instead of property


I like to add some data to a column, from a method instead of a property. Is this somehow possible in EF Core?

For example, the config code could look like this:

internal class MyEntityTypeConfiguration : IEntityTypeConfiguration<MyEntity>
{
    public void Configure(EntityTypeBuilder<MyEntity> builder)
    {
        builder.ToTable("Table1");

        // Add column "Value1" and set it with the return value of myEntity.GetValue()
        builder.Property<string>("Value1").WithValue(myEntity => myEntity.GetValue()); // TODO create WithValue

        builder.HasKey(o => o.Id);
    }
}

in this case, the WithValue method won't exist.

Example:

For example, I will save 2 entities.

  1. GetValue() for entity 1 returns "I am Entity 1"
  2. GetValue() for entity 2 returns "I am Entity 2"

Then I like store "I am Entity 1" and "I am Entity 2" in the column Value1

Solution

Jairo's solution with the ValueGenerator worked perfect for me! I made the WithValue like this:

internal class ValueRetriever<TEntityEntry, TResult> : Microsoft.EntityFrameworkCore.ValueGeneration.ValueGenerator<TResult>
{
    private readonly Func<TEntityEntry, TResult> _retrieve;

    public ValueRetriever(Func<TEntityEntry, TResult> retrieve)
    {
        _retrieve = retrieve;
    }

    public override bool GeneratesTemporaryValues => false;

    public override TResult Next(EntityEntry entry) => _retrieve((TEntityEntry)entry.Entity);
}

WithValue extension:

public static void WithValue<TEntityEntry, TResult>(this PropertyBuilder<TResult> propertyBuilder, Func<TEntityEntry, TResult> retrieve)
{
    propertyBuilder.HasValueGenerator((property, type) => new ValueRetriever<TEntityEntry, TResult>(retrieve));
}

Usage:

builder
   .Property<string>("Value1")
   .WithValue<MyEntity, string>(myEntity => myEntity.GetValue()); 

Solution

  • I think shadow properties can help you.

    EF Core shadow properties let you define & persist non-domain data, data that are not defined in your classes. You define shadow properties in your DbContext, and you use the DbContext to set their values.

    To define them:

    modelBuilder.Entity<MyEntity>().Property<String>("Value1");
    

    To set their values:

    dbContext.Entry(myEntity).Property("Value1").CurrentValue = myEntity.GetValue();
    

    Also, you can use the HasValueGenerator extension method to set a value generator that can get the value from your entity:

    modelBuilder.Entity<MyEntity>().Property<string>("Value1").HasValueGenerator<ValueGenerator>();
    

    The Value Generator:

    class ValueGenerator : Microsoft.EntityFrameworkCore.ValueGeneration.ValueGenerator
    {
        public override bool GeneratesTemporaryValues => false;
    
        protected override object NextValue(EntityEntry entry) => ((MyEntity) entry.Entity).GetValue();
    }
    

    The entity:

    class MyEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string GetValue() => $"My Name: {Name}";
    }