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

Mechanism/API to specify a default conversion for any property of a given type in the model #10784

Closed
Tracked by #24107 ...
ajcvickers opened this issue Jan 27, 2018 · 46 comments · Fixed by #25025
Closed
Tracked by #24107 ...
Labels
area-conventions area-model-building area-o/c-mapping area-type-mapping closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. punted-for-5.0 type-enhancement
Milestone

Comments

@ajcvickers
Copy link
Contributor

Value conversions were introduced by #242. Currently conversions are only set per-property, although bulk configuration can be used at the end of OnModelCreating in the normal way. This issue is about adding some kind of mechanism to set the conversion for all properties of a given type in the model. This could be through configuration on the model, or it could be via improved bulk config APIs.

@ajcvickers
Copy link
Contributor Author

Note that the converter isn't really for "every property" even though it will also have that effect. It should also be for any time the CLR type is used in the query--that is, it is really a custom type mapping setup by the application.

@bugproof
Copy link

bugproof commented Aug 24, 2018

As a temporary workaround you can use this extension method(I think it works):

public static class ModelBuilderExtensions
{
    public static ModelBuilder UseValueConverterForType<T>(this ModelBuilder modelBuilder, ValueConverter converter)
    {
        return modelBuilder.UseValueConverterForType(typeof(T), converter);
    }

    public static ModelBuilder UseValueConverterForType(this ModelBuilder modelBuilder, Type type, ValueConverter converter)
    {
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            // note that entityType.GetProperties() will throw an exception, so we have to use reflection 
            var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type);
            foreach (var property in properties)
            {
                modelBuilder.Entity(entityType.Name).Property(property.Name)
                    .HasConversion(converter);
            }
        }

        return modelBuilder;
    }
}

@ajcvickers
Copy link
Contributor Author

@ZeroNightzz What exception is thrown by entityType.GetProperties()?

@bugproof
Copy link

@ajcvickers

System.InvalidOperationException: The property 'CurrencyExchangeRate.Currency' could not be mapped, because it is of type 'Currency' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.PropertyMappingValidationConvention.Apply(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelBuilt(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelBuilt(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.Validate()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass5_0.<GetModel>b__1()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_1(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   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()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Internal.InternalAccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_1.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
The property 'CurrencyExchangeRate.Currency' could not be mapped, because it is of type 'Currency' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

this is caused, so I have to use GetProperties() by accessing ClrType. I think the conversion must be known before using GetProperties() defined in EF.

@ajcvickers
Copy link
Contributor Author

@Necronux Thanks. I thought you were indicating an exception when calling the GetProperties method itself.

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Nov 30, 2018

The bulk configuration should be performed before set discovery as it could affect whether a property would be considered a navigation. See #12229

ghost pushed a commit that referenced this issue Jun 9, 2021
…#25025)

Allow to specify a value conversion and other facets for all properties of a given type

Fixes #3867
Fixes #10784
@vanillajonathan
Copy link
Contributor

@AndriySvyryd Could you explain how to configure EF Core to use a default conversion for any property of a given type in the model?

@roji
Copy link
Member

roji commented Jun 9, 2021

@vanillajonathan this new feature was just merged, and will only be available in 6.0 previews. For 5.0, there's a good chance you can do something similar to #10784 (comment) as a workaround.

@AndriySvyryd
Copy link
Member

For EF Core 6.0.0-preview6:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<string>().HaveConversion<byte[]>()
}

@julielerman
Copy link

julielerman commented Aug 20, 2021

Hi @AndriySvyryd , I'm tring to use the HaveConversion method to pass in the two transofmration lambdas ala

configurationBuilder.Properties().HaveConversion(c=>c.ToString(),s=>Color.FromName(s));

but the compiler is complaining. I looked at source and don't see an overload that matches the signature I use to do this with HasConversion:

(Expression<Func<TProperty, TProvider>> convertToProviderExpression, Expression<Func<TProvider, TProperty>> convertFromProviderExpression);

Is this coming in EF Core 6? Thanks

NOTE: I can achieve this by creating my own ValueConverter class. it's just the handy shortcut that's missing for now. :)

@ajcvickers
Copy link
Contributor Author

@julielerman You need to create an actual ValueConverter class when using it for all properties like this. This isn't something that is planned to change.

@julielerman
Copy link

Thanks. Yeah I figured that out (I think I wrote my edit at the same time ou wrote this response. LOL. The expression overload would certainly be handy though....someday.

@AndriySvyryd
Copy link
Member

@julielerman That overload wouldn't be compatible with compiled models, so we discourage using it (by not providing it in the new API)

@julielerman
Copy link

ohhh! Right. Now have to keep compiled models in mind for all the configs! Thanks @AndriySvyryd

@vanillajonathan
Copy link
Contributor

The value conversions article needs to be updated.

@Dynalon
Copy link

Dynalon commented Feb 10, 2022

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<string>().HaveConversion<byte[]>()
}

As @vanillajonathan outlined, this really should go into the value conversions article for documentation purposes. The only mention to that addition I could find was the 6.0.0-preview6 release blog article, and it took me a while to find it - even so it is perfectly what I needed.

@roji
Copy link
Member

roji commented Feb 10, 2022

@Dynalon @vanillajonathan I've added a bulk configuration section to the value conversions page in dotnet/EntityFramework.Docs#3716.

@thomaslevesque
Copy link
Member

@Dynalon @vanillajonathan I've added a bulk configuration section to the value conversions page in #3716.

Wrong link? #3716 doesn't seem to be related to this..

@roji
Copy link
Member

roji commented Feb 10, 2022

Sorry, wrong repo - corrected above.

@ronnyek
Copy link

ronnyek commented Feb 16, 2022

Is there a decent workaround for efcore versions < 6? We dont have the ability to upgrade to .net 6 just yet, and have a domain model of thousands of tables that have used the old oracle convention of using bool as char(1) with constraints to Y/N. Something a value converter exists for, but is gonna make mapping this entities a royal nightmare.

@roji
Copy link
Member

roji commented Feb 17, 2022

@ronnyek take a look at Bulk configuration in OnModelCreating, that may work for you. Note also that EF Core 5.0 goes out of support in May, and 3.1 in December - so I'd recommend upgrading soon instead.

@igbenic

This comment was marked as resolved.

@igbenic

This comment was marked as resolved.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-conventions area-model-building area-o/c-mapping area-type-mapping closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. punted-for-5.0 type-enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.