innofactor / efcorejsonvalueconverter Goto Github PK
View Code? Open in Web Editor NEWJSON ValueConverter for EF Core 3.0+
License: GNU Lesser General Public License v3.0
JSON ValueConverter for EF Core 3.0+
License: GNU Lesser General Public License v3.0
I'm not sure if this is an issue with the library or EF Core itself, but it may be worth being aware of at least.
If you use HasJsonConversion()
on a property of an owned type and you replace the owned type (e.g. because you're using immutable value objects, so you cannot modify the properties directly), the Json property is always recognized as a change even if its property value is the same as the original so the field is always included in an update to the database (I'm using SQL Server for storage).
Extremely basic example:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Entity>().Property(e => e.Items).HasJsonValueConversion();
modelBuilder.Entity<Entity>().OwnsOne(e => e.Owned, b =>
{
b.Property(o => o.Items).HasJsonValueConversion();
});
}
public DbSet<Entity> Entities { get; set; }
}
public class Entity
{
public int Id { get; set; }
public string Field { get; set; }
public List<string> Items { get; set; }
public Owned Owned { get; set; }
}
public class Owned
{
public string Field { get; set; }
public List<string> Items { get; set; }
}
Assume you already have an entity saved in the database that is equivalent to:
new Entity
{
Id = 1,
Field = "string",
Items = new List<string>() { "Hello", "World!" },
Owned = new Owned
{
Field = "string",
Items = new List<string>() { "Homer", "Simpson" }
}
};
On the root entity itself, everything works as expected when assigning an equivalent value to the property:
// not sent as update, db value is the same
entity.Items = new List<string>() { "Hello", "World!" };
On the owned entity, if you reassign the property without replacing the owned object itself, again everything works as expected:
// not sent as update, db value is the same
entity.Owned.Items = new List<string>() { "Homer", "Simpson" };
If you replace the owned entity, then the value is always considered to be modified, even though regular values (string Field) are correctly handled:
entity.Owned = new Owned
{
// not sent as update even though owned entity is replaced, db value is the same
Field = "string" ,
// SENT AS UPDATE, db value is same
Items = new List<string>() { "Homer", "Simpson" };
}
Even if you reassign the value to the exact same reference, it's still treated as modified:
var owned = entity.Owned;
entity.Owned = new Owned
{
// not sent as update even though owned entity is replaced, db value is the same
Field = owned.Field,
// SENT AS UPDATE, db value is same
Items = owned.Items
}
It's almost like the change tracker is not taking into consideration the ValueComparer
when fixing up the owned object's properties after a reassignment. Do you think there is anything the library could do to help here?
If I could find out which properties use the Finding the properties with a JsonValueComparer
/JsonValueConverter
while looping through ChangeTracker.Entries
I could probably fix up the properties, but I don't know how to find out if a property has a ValueConverter
/ValueComparer
with the metadata that is available at that point.JasonValueComparer
is easy enough, but the properties are not marked as modified, so I'm not sure why they are still flowing to the database update.
I get the following when having primitives (int32, bool) with [JsonField] attributes when using EF Add-Migration:
System.ArgumentException: GenericArguments[0], 'System.Int32', on 'Innofactor.EfCoreJsonValueConverter.JsonValueConverter1[T]' violates the constraint of type 'T'. ---> System.TypeLoadException: GenericArguments[0], 'System.Int32', on 'Innofactor.EfCoreJsonValueConverter.JsonValueConverter
1[T]' violates the constraint of type parameter 'T'.
at System.RuntimeTypeHandle.Instantiate(Type[] inst)
at System.RuntimeType.MakeGenericType(Type[] instantiation)
--- End of inner exception stack trace ---
at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
at System.RuntimeType.MakeGenericType(Type[] instantiation)
at Innofactor.EfCoreJsonValueConverter.ModelBuilderExtensions.AddJsonFields(ModelBuilder modelBuilder, Boolean skipConventionalEntities)
at Appva.Auth.Clients.Api.Models.ClientDbContext.OnModelCreating(ModelBuilder modelBuilder) in C:\development\auth-clients-api\src\Appva.Auth.Clients.Api\Appva.Auth.Clients.Api\Models\ClientDbContext.cs:line 27
at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.b__7_3(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor
2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor
2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.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.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure1 accessor) at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure
1 accessor)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func1 factory) at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType) at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace) at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace) at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0() at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0
1.b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Hi there, this is not an issue and more a question!
Do you think it is possible to use this library for localization?
I asked a question here, and I think it is possible to extend your library for this use-case!
Object change detection does not work with ChangeTrackingStrategy.ChangingAndChangedNotifications
because JsonValueComparer
is not called. JsonValueComparer
is only called with ChangeTrackingStrategy.Snapshot
.
Is there a way to detect changes with ChangeTrackingStrategy.ChangingAndChangedNotifications
?
Sometimes i need to use the pure json string from DB. Can you provide some extension for JsonField to avoid serialization?
After upgrading the project to NET 6 i've got:
MissingMethodException: Method not found: 'System.Type Microsoft.EntityFrameworkCore.Metadata.ITypeBase.get_ClrType()'.
Maybe, the problem is with Entity Framework Core 6
Just wanted to say that your converter works great but unfortunately in my case I can't use it due to the license you have assigned to it. I work for a company that don't want to disclose its software products. Maybe in the near future could you please consider moving it to another one like MIT? Regards.
I was wondering why the project was upgraded to .net standard 2.1 instead of staying on 2.0?
This breaks compatibility with projects that need to support .net framework.
EF does not detect changes to complex JSON property. Related discussion: dotnet/efcore#10969
I also created another repro project: https://github.com/jukkahyv/efcore-valueconverter-complextype-changetracking
Issue appeared after moving to use clr-types for initializing json properties without explicit entity.Property(...) calls (issue #2 ). Since call to builder.AddJsonFields initializes properties besides their valueconverters, we should filter out properties that are marked as ignored. Otherwise it will result in failed model state.
Hi,
Thanks for a wonderful library, it makes job easier and clean code is a bonus. I have customer converters for some objects. How can we make use of it?
Thanks,
Attiqe
It's possible to customise JSON serialisation at a global level by changing JsonConvert.DefaultSettings
, but it would be good to have the option to customise it just for EF Core JSON serialisation by specifying a JsonSerializerSettings
, for example to prevent cyclic references from being serialised, configure null handling, or to add type information to the serialised JSON to support interfaces:
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.Objects,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
};
var json = JsonConvert.SerializeObject(obj, jsonSettings);
var obj = JsonConvert.DeserializeObject<T>>(json, jsonSettings);
At it's most basic, this could be implemented as a global setting by adding a static JsonSerializerSettings
in this library (or a configuration class that composes it). I imagine a global setting would be what most people would want.
Alternatively (or additionally), the HasJsonValueConversion
extension method could have an optional JsonSerializerSettings
argument added to it. Not sure if there would really be a need to configure it for each property though.
Problem is that without the call to Property(), EF recognizes JSON fields as navigation properties instead of plain properties and tries to look for a primary key.
System.InvalidOperationException: The entity type 'Address' requires a primary key to be defined.
For now, I cannot find a way to remove the navigation property from the entity type.
Related to dotnet/efcore#11199
Package Innofactor.EfCoreJsonValueConverter 2.0.2 is not compatible with netcoreapp3.1 (.NETCoreApp,Version=v3.1).
Package Innofactor.EfCoreJsonValueConverter 2.0.2 supports: netstandard2.1 (.NETStandard,Version=v2.1).
I uninstalled the dotnet sdk and reinstalled the sdk, and then an error occurred.
net core sdk: 3.1.300
net core: 3.1.4
Package Innofactor.EfCoreJsonValueConverter 2.0.2 is not compatible with netcoreapp3.1 (.NETCoreApp,Version=v3.1).
Package Innofactor.EfCoreJsonValueConverter 2.0.2 supports: netstandard2.1 (.NETStandard,Version=v2.1).
I uninstalled the dotnet sdk and reinstalled the sdk, and then an error occurred.
net core sdk: 3.1.300
net core: 3.1.4
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.