Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using convertion for ValueObject will throw InvalidOperationException #10765

Closed
glucaci opened this issue Jan 25, 2018 · 6 comments
Closed

Using convertion for ValueObject will throw InvalidOperationException #10765

glucaci opened this issue Jan 25, 2018 · 6 comments
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@glucaci
Copy link
Contributor

glucaci commented Jan 25, 2018

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 bf03e18)
Database Provider: (Microsoft.EntityFrameworkCore.SqlServer 2.1.0-preview1 local build)

@ajcvickers ajcvickers self-assigned this Jan 25, 2018
@ajcvickers ajcvickers added this to the 2.1.0-preview1 milestone Jan 25, 2018
ajcvickers added a commit that referenced this issue Jan 26, 2018
…e known

Issue #10765

Because until the call to HasConversion is made we can't know whether or not the property can be mapped.
ajcvickers added a commit that referenced this issue Jan 26, 2018
…e known

Issue #10765

Because until the call to HasConversion is made we can't know whether or not the property can be mapped.
ajcvickers added a commit that referenced this issue Jan 26, 2018
…e known

Issue #10765

Because until the call to HasConversion is made we can't know whether or not the property can be mapped.
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jan 26, 2018
@Devy-Devly
Copy link

Devy-Devly commented Mar 11, 2018

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.

@ajcvickers
Copy link
Member

@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.

@mknet3
Copy link

mknet3 commented Jul 10, 2018

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.

@mknet3
Copy link

mknet3 commented Jul 10, 2018

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!

@AndrewBoklashko
Copy link

@manuelcaub what you actually need here is

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.

@ajcvickers
Copy link
Member

@manuelcaub Your code defines as DbSet for Emails:

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.

@ajcvickers ajcvickers modified the milestones: 2.1.0-preview1, 2.1.0 Nov 11, 2019
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

5 participants