json.net - ¿Cómo almacenar JSON en un campo de entidad con EF Core?
entity-framework-core (5)
¿Podrías intentar algo como esto?
[NotMapped]
private JObject extraData;
[NotMapped]
public JObject ExtraData
{
get { return extraData; }
set { extraData = value; }
}
[Column("ExtraData")]
public string ExtraDataStr
{
get
{
return this.extraData.ToString();
}
set
{
this.extraData = JsonConvert.DeserializeObject<JObject>(value);
}
}
Aquí está la salida de la migración:
ExtraData = table.Column<string>(nullable: true),
Estoy creando una biblioteca reutilizable utilizando .NET Core (orientada a .NETStandard 1.4) y estoy utilizando Entity Framework Core (y nuevo para ambos) Tengo una clase de entidad que se parece a:
public class Campaign
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public JObject ExtendedData { get; set; }
}
y tengo una clase DbContext que define el DbSet:
public DbSet<Campaign> Campaigns { get; set; }
(También estoy usando el patrón de Repositorio con DI, pero no creo que sea relevante).
Mis pruebas unitarias me dan este error:
System.InvalidOperationException: no se puede determinar la relación representada por la propiedad de navegación ''JToken.Parent'' de tipo ''JContainer''. Configure manualmente la relación o ignore esta propiedad del modelo.
¿Hay alguna forma de indicar que esto no es una relación pero debe almacenarse como una cadena grande?
Tenga cuidado con este enfoque: EF Core marca una entidad como modificada solo si el campo está asignado a . Entonces, si usa person.Addresses.Add, la entidad no se marcará como actualizada; deberá llamar al establecedor de propiedades person.Addresses = updatedAddresses.
me hizo adoptar un enfoque diferente para que este hecho sea obvio: use los métodos de Getter y Setter, en lugar de una propiedad.
public void SetExtendedData(JObject extendedData) {
ExtendedData = JsonConvert.SerializeObject(extendedData);
_deserializedExtendedData = extendedData;
}
//just to prevent deserializing more than once unnecessarily
private JObject _deserializedExtendedData;
public JObject GetExtendedData() {
if (_extendedData != null) return _deserializedExtendedData;
_deserializedExtendedData = string.IsNullOrEmpty(ExtendedData) ? null : JsonConvert.DeserializeObject<JObject>(ExtendedData);
return _deserializedExtendedData;
}
Teóricamente podrías hacer esto:
campaign.GetExtendedData().Add(something);
Pero es mucho más claro que eso no hace lo que piensas que hace ™.
Si usa la base de datos primero y usa algún tipo de generador automático de clase para EF, entonces las clases generalmente se declararán como partial
, por lo que puede agregar estas cosas en un archivo separado que no se perderá la próxima vez Actualizas tus clases desde tu base de datos.
La respuesta de @ Michael me puso en el camino, pero lo implementé de manera un poco diferente. Terminé almacenando el valor como una cadena en una propiedad privada y usándolo como un "Campo de respaldo". La propiedad ExtendedData luego convirtió JObject en una cadena en conjunto y viceversa en obtener:
public class Campaign
{
// https://docs.microsoft.com/en-us/ef/core/modeling/backing-field
private string _extendedData;
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[NotMapped]
public JObject ExtendedData
{
get
{
return JsonConvert.DeserializeObject<JObject>(string.IsNullOrEmpty(_extendedData) ? "{}" : _extendedData);
}
set
{
_extendedData = value.ToString();
}
}
}
Para establecer _extendedData
como un campo de respaldo, agregué esto a mi contexto:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Campaign>()
.Property<string>("ExtendedDataStr")
.HasField("_extendedData");
}
Actualización: la respuesta de Darren para usar las conversiones de EF Core Value (nuevas en EF Core 2.1 - que no existían en el momento de esta respuesta) parece ser la mejor manera de llegar a este punto.
Para aquellos que usan EF 2.1, hay un pequeño paquete de EfCoreJsonValueConverter que lo hace bastante simple.
using Innofactor.EfCoreJsonValueConverter;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class Campaign
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public JObject ExtendedData { get; set; }
}
public class CampaignConfiguration : IEntityTypeConfiguration<Campaign>
{
public void Configure(EntityTypeBuilder<Campaign> builder)
{
builder
.Property(application => application.ExtendedData)
.HasJsonValueConversion();
}
}
Voy a responder a esta de manera diferente.
Lo ideal es que el modelo de dominio no tenga idea de cómo se almacenan los datos. Agregar campos de respaldo y propiedades extra [NotMapped]
en realidad está acoplando su modelo de dominio a su infraestructura.
Recuerda: tu dominio es el rey, y no la base de datos. La base de datos solo se utiliza para almacenar partes de su dominio.
En su lugar, puede usar el método HasConversion()
EF Core en el objeto EntityTypeBuilder
para convertir entre su tipo y JSON.
Teniendo en cuenta estos 2 modelos de dominio:
public class Person
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[Required]
public DateTime DateOfBirth { get; set; }
public IList<Address> Addresses { get; set; }
}
public class Address
{
public string Type { get; set; }
public string Company { get; set; }
public string Number { get; set; }
public string Street { get; set; }
public string City { get; set; }
}
Solo he agregado atributos en los que está interesado el dominio, y no detalles que le interesen a la base de datos; IE no hay [Key]
.
Mi DbContext tiene la siguiente IEntityTypeConfiguration
para la Person
:
public class PersonsConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
// This Converter will perform the conversion to and from Json to the desired type
builder.Property(e => e.Addresses).HasConversion(
v => JsonConvert.SerializeObject(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }),
v => JsonConvert.DeserializeObject<IList<Address>>(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));
}
}
Con este método puede desacoplar completamente su dominio de su infraestructura. No es necesario para todos los campos de respaldo y propiedades adicionales.