C# - Using Entity Framework Core 3 HasConversion to convert a field to JSON in .Net Core 3.1

↘锁芯ラ 提交于 2021-01-28 06:02:39

问题


I was trying to accomplish the conversion dynamically across all of the models I have in my project:

DbContext.cs

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var entityTypes = modelBuilder.Model.GetEntityTypes().ToList();
    foreach (var entityType in entityTypes)
    {
        foreach (var property in entityType.ClrType.GetProperties().Where(x => x != null && x.GetCustomAttribute<HasJsonConversionAttribute>() != null))
        {
            modelBuilder.Entity(entityType.ClrType)
                .Property(property.PropertyType, property.Name)
                .HasJsonConversion();
        }
    }

    base.OnModelCreating(modelBuilder);
}

Then created a data annotation attribute to mark my fields in my model as "Json"

public class HasJsonConversionAttribute : Attribute {}

This is my extension method used to convert the Json to object and back to Json

public static class SqlExtensions
{
    public static PropertyBuilder HasJsonConversion(this PropertyBuilder propertyBuilder)
    {
        ParameterExpression parameter1 = Expression.Parameter(propertyBuilder.Metadata.ClrType, "v");

        MethodInfo methodInfo1 = typeof(Newtonsoft.Json.JsonConvert).GetMethod("SerializeObject", types: new Type[] { typeof(object) });
        MethodCallExpression expression1 = Expression.Call(methodInfo1 ?? throw new Exception("Method not found"), parameter1);

        ParameterExpression parameter2 = Expression.Parameter(typeof(string), "v");
        MethodInfo methodInfo2 = typeof(Newtonsoft.Json.JsonConvert).GetMethod("DeserializeObject", 1, BindingFlags.Static | BindingFlags.Public, Type.DefaultBinder, CallingConventions.Any, types: new Type[] { typeof(string) }, null)?.MakeGenericMethod(propertyBuilder.Metadata.ClrType) ?? throw new Exception("Method not found");
        MethodCallExpression expression2 = Expression.Call(methodInfo2, parameter2);
        
        var converter = Activator.CreateInstance(typeof(ValueConverter<,>)
                                 .MakeGenericType(propertyBuilder.Metadata.ClrType, typeof(string)), new object[]
        {
            Expression.Lambda( expression1,parameter1),
            Expression.Lambda( expression2,parameter2),
            (ConverterMappingHints) null
        });

        propertyBuilder.HasConversion(converter as ValueConverter);
        return propertyBuilder;
    }
}

For simplicity I'm using this User model:

public class User : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        // Apply some settings defined in custom annotations in the model properties
        //builder.ApplyCustomAnnotationsAndConfigs(this);
    }
    
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Username { get; set; }

    [HasJsonConversion]
    public List<LocalizedName> Values { get; set; }
}

and This is the class that I want to convert to and from JSON:

public class LocalizedName
{
    public string Value { get; set; }
    public string Code { get; set; }
}

Now here's the problem I'm facing, it keeps on detecting LocalizedName object as another model that doesn't have a Key and throws an error telling me to add a Key/PK even though this is not flagged as a model.

Now if I execute this from the User -> Configure() it will work BUT it'll show me other issues like the model loses its own relationships and associations with other models and now it throws me other set of errors which I didn't even have in the first place.

I've also noticed that EF removes LocalizedName from the property list and shows it under Navigation properties list. And lastly, I've checked the OnModelCreating -> modelBuilder.Model.GetEntityTypes() and noticed that EF is treating it as a new Model which is kinda weird.

Is there a way I can tell EF to mark this as a string/json field instead of EF auto-assuming it's a model?

Any help would be appreciated.


回答1:


You can ignore your entity in OnModelCreating like this:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Ignore<LocalizedName>();
   //rest of your code
}

And for ignore all model that has HasJsonConversion Attribute you can do this (I didn't test this):

       var entityTypes = modelBuilder.Model.GetEntityTypes().ToList();
        foreach (var entityType in entityTypes)
        {
            foreach (var property in entityType.ClrType.GetProperties().Where(x => x != null && x.GetCustomAttribute<HasJsonConversionAttribute>() != null))
            {
                modelBuilder.Entity(entityType.ClrType)
                    .Property(property.PropertyType, property.Name)
                    .HasJsonConversion();

                modelBuilder.Ignore(property.PropertyType);
            }
        }



回答2:


So I ended up using this NuGet package from https://github.com/Innofactor/EfCoreJsonValueConverter

Then in my DbContext.cs:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Ignore<LocalizedName>(); //<--- Ignore this model from being added by convention
    modelBuilder.AddJsonFields(); //<--- Auto add all json fields in all models
}

Then in my User model:

public class User : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder) {}
    
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Username { get; set; }

    [JsonField] //<------ Add this attribute
    public List<LocalizedName> Values { get; set; }
}

Now it works as expected.



来源:https://stackoverflow.com/questions/65745810/c-sharp-using-entity-framework-core-3-hasconversion-to-convert-a-field-to-json

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!