Efcore: Using convertion for ValueObject will throw InvalidOperationException

Created on 25 Jan 2018  路  6Comments  路  Source: dotnet/efcore

Steps to reproduce

Having the following model

public class User
{
   public User(Email email)
   {
       Id = Guid.NewGuid();
       Email = email;
   }

   public Guid Id { get; private set; }
   public Email Email { get; private set; }
}

public class Email
{
   private readonly string _value;
   private Email(string value)
   {
       _value = value;
   }

   public static Email Create(string value)
   {
       // some validation
       return new Email(value);
   }

   public static implicit operator string(Email email) => email._value;
}

and the configuration

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>(builder =>
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Email).HasConversion(
            new ValueConverter<Email, string>(email => email, value => Email.Create(value)));
    });
}

it will throw:

System.InvalidOperationException
  HResult=0x80131509
  Message=Cannot call Property for the property 'Email' on entity type 'Customer' because it is configured as a navigation property. Property can only be used to configure scalar properties.
  Source=Microsoft.EntityFrameworkCore
  StackTrace:
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.Property(Property existingProperty, String propertyName, Type propertyType, MemberInfo clrProperty, Nullable`1 configurationSource, Nullable`1 typeConfigurationSource) in ..EFCoreRepo\src\EFCore\Metadata\Internal\InternalEntityTypeBuilder.cs:line 421
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.Property(String propertyName, Type propertyType, MemberInfo memberInfo, Nullable`1 configurationSource, Nullable`1 typeConfigurationSource) in ..EFCoreRepo\src\EFCore\Metadata\Internal\InternalEntityTypeBuilder.cs:line 385
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.Property(MemberInfo clrProperty, ConfigurationSource configurationSource) in ..EFCoreRepo\src\EFCore\Metadata\Internal\InternalEntityTypeBuilder.cs:line 341
   at Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1.Property[TProperty](Expression`1 propertyExpression) in ..EFCoreRepo\src\EFCore\Metadata\Builders\EntityTypeBuilder`.cs:line 121
   at EFCore.StoreContext.<>c.<OnModelCreating>b__1_0(EntityTypeBuilder`1 builder) in ..\EFCore\Tests.cs:line 45
   at Microsoft.EntityFrameworkCore.ModelBuilder.Entity[TEntity](Action`1 buildAction) in ..EFCoreRepo\src\EFCore\ModelBuilder.cs:line 134
   at EFCore.StoreContext.OnModelCreating(ModelBuilder modelBuilder) in ..\EFCore\Tests.cs:line 42
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context) in ..EFCoreRepo\src\EFCore\Infrastructure\ModelCustomizer.cs:line 52
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context) in ..EFCoreRepo\src\EFCore.Relational\Infrastructure\RelationalModelCustomizer.cs:line 61
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator) in ..EFCoreRepo\src\EFCore\Infrastructure\ModelSource.cs:line 79
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass5_0.<GetModel>b__0(Object k) in ..EFCoreRepo\src\EFCore\Infrastructure\ModelSource.cs:line 56
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator) in ..EFCoreRepo\src\EFCore\Infrastructure\ModelSource.cs:line 54
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel() in ..EFCoreRepo\src\EFCore\Internal\DbContextServices.cs:line 72
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model() in ..EFCoreRepo\src\EFCore\Internal\DbContextServices.cs:line 94
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_1(IServiceProvider p) in ..EFCoreRepo\src\EFCore\Infrastructure\EntityFrameworkServicesBuilder.cs:line 252
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() in ..EFCoreRepo\src\EFCore\DbContext.cs:line 316
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider() in ..EFCoreRepo\src\EFCore\DbContext.cs:line 304
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() in ..EFCoreRepo\src\EFCore\DbContext.cs:line 316
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityStates(IEnumerable`1 entities, EntityState entityState) in ..EFCoreRepo\src\EFCore\DbContext.cs:line 1126
   at Microsoft.EntityFrameworkCore.DbContext.AddRange(IEnumerable`1 entities) in ..EFCoreRepo\src\EFCore\DbContext.cs:line 1144
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.AddRange(IEnumerable`1 entities) in ..EFCoreRepo\src\EFCore\Internal\InternalDbSet.cs:line 217
   at EFCore.Tests.Main() in ..\EFCore\Tests.cs:line 22

Please feel free to change the name if it's not appropriate.

Further technical details

EF Core version: (2.1.0-preview1 local build with last commit bf03e185f40d05442d031acd5d62bf4f27bb94bb)
Database Provider: (Microsoft.EntityFrameworkCore.SqlServer 2.1.0-preview1 local build)

closed-fixed type-bug

All 6 comments

using 2.1.0-preview1-final;

I found I had problems creating my context if I also Owned another property that was listed after the Conversion, i.e. order of the builder code is relevant
e.g.
The following fails:
builder.Property(x => x.Email).HasConversion( new ValueConverter<Email, string>(email => email, value => Email.Create(value)));
builder.OwnsOne(p => p.OtherEmailType).Property(p => p.Value).HasColumnName("OtherEmailType");

But the following succeeds:
builder.OwnsOne(p => p.OtherEmailType).Property(p => p.Value).HasColumnName("OtherEmailType");
builder.Property(x => x.Email).HasConversion( new ValueConverter<Email, string>(email => email, value => Email.Create(value)));

Ta, G.

@Devy-Devly Can you please file a new issue for this, including full details of how to reproduce the problem and version, etc. as requested in the issue template.

Following the example of @glucaci , I am trying to convert a custom type Email to string with ValueConverter (Microsoft.EntityFrameworkCore 2.1.1):

```
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext()
{
this.Database.EnsureDeleted();
this.Database.EnsureCreated();
}

    public DbSet<Email> Emails { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder builder)
    {
        builder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TestValueConverter; Integrated Security = SSPI;MultipleActiveResultSets=true;Connect Timeout=10;");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>(builder =>
        {
            builder.HasKey(x => x.Id);
            builder.Property(x => x.Email).HasConversion(
                new ValueConverter<Email, string>(email => email, value => Email.Create(value)));
        });
    }
}

public class User
{
    public Guid Id { get; set; }

    public Email Email { get; set; }
}

public class Email
{
    private readonly string value;

    private Email() { }

    private Email(string value)
    {
        this.value = value;
    }

    public static implicit operator string(Email email) => email.value;

    public static Email Create(string value)
    {
        return new Email(value);
    }
}

```

it throw:

System.InvalidOperationException - The entity type 'Email' requires a primary key to be defined.

This issue is closed because #242 but... this is not working for me.

Resolved, if I write:

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Ignore<Email>();

            modelBuilder.Entity<User>(builder =>
            {
                builder.HasKey(x => x.Id);
                builder.Property(x => x.Email).HasConversion(
                    new ValueConverter<Email, string>(email => email, value => Email.Create(value)));
            });
        }

modelBuilder.Ignore(); works fine!

@manuelcaub what you actually need here is
C# builder.OwnsOne(x => x.Email).HasConversion( new ValueConverter<Email, string>(email => email, value => Email.Create(value)));

The exception is because by convention EF Core recognizes every type you mention in OnModelCreating as an entity type and those require a primary key to be defined.

You should use OwnsOne API to let EF Core know, that Email property is actually a value object and its value should be treated as a part of User entity.

@manuelcaub Your code defines as DbSet for Emails:
C# public DbSet<Email> Emails { get; set; }
This tells EF that Email is an entity type, which is incorrect since it is in this case just a scalar property of another entity type.

Was this page helpful?
0 / 5 - 0 ratings