Je crée une bibliothèque réutilisable à l'aide de .NET Core (ciblant .NETStandard 1.4) et j'utilise Entity Framework Core (et nouvelle pour les deux). J'ai une classe d'entité qui ressemble à:
public class Campaign
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public JObject ExtendedData { get; set; }
}
et j'ai une classe DbContext qui définit le DbSet:
public DbSet<Campaign> Campaigns { get; set; }
(J'utilise également le modèle Repository avec DI, mais je ne pense pas que ce soit pertinent.)
Mes tests unitaires me donnent cette erreur:
System.InvalidOperationException: Impossible de déterminer la relation représentée par la propriété de navigation 'JToken.Parent' de type "JContainer". Configurez manuellement la relation ou ignorez cette propriété du modèle ..
Existe-t-il un moyen d'indiquer qu'il ne s'agit pas d'une relation mais qu'il devrait être stocké sous la forme d'une grosse chaîne?
La réponse de @ Michael m'a mis sur la bonne voie, mais je l'ai mise en œuvre un peu différemment. J'ai fini par stocker la valeur en tant que chaîne dans une propriété privée et à l'utiliser comme "champ de sauvegarde". La propriété ExtendedData a ensuite converti JObject en une chaîne sur set et vice-versa sur get:
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();
}
}
}
Pour définir _extendedData
comme champ de base, j'ai ajouté ceci à mon contexte:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Campaign>()
.Property<string>("ExtendedDataStr")
.HasField("_extendedData");
}
Mise à jour: la réponse de Darren à utiliser EF Core Value Conversions (nouvelle version d'EF Core 2.1 - qui n'existait pas à l'époque de cette réponse) semble être la meilleure solution à ce stade.
Va répondre à celui-ci différemment.
Idéalement, le modèle de domaine ne devrait avoir aucune idée de la manière dont les données sont stockées. L'ajout de champs de sauvegarde et de propriétés [NotMapped]
supplémentaires consiste à coupler votre modèle de domaine à votre infrastructure.
N'oubliez pas que votre domaine est roi et non la base de données. La base de données est simplement utilisée pour stocker des parties de votre domaine.
À la place, vous pouvez utiliser la méthode HasConversion()
de EF Core sur l'objet EntityTypeBuilder
pour convertir votre type en JSON.
Étant donné ces 2 modèles de domaine:
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; }
}
J'ai seulement ajouté les attributs qui intéressent le domaine - et non les détails qui intéresseraient la base de données; I.E il n'y a pas [Key]
.
Mon DbContext a la IEntityTypeConfiguration
suivante pour 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 }));
}
}
Avec cette méthode, vous pouvez complètement découpler votre domaine de votre infrastructure. Pas besoin de tous les champs et propriétés supplémentaires.
Pourriez-vous essayer quelque chose comme ça?
[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);
}
}
voici le résultat de la migration:
ExtraData = table.Column<string>(nullable: true),
Pour ceux qui utilisent EF 2.1, il existe un joli petit paquet NuGet EfCoreJsonValueConverter qui le rend assez 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();
}
}
Soyez prudent avec cette approche: EF Core marque une entité comme modifiée uniquement si le champ est affecté à. Donc, si vous utilisez person.Addresses.Add, l'entité ne sera pas marquée comme mise à jour; vous devez appeler la propriété setter property.Addresses = updatedAddresses.
m'a fait adopter une approche différente pour que ce fait soit évident: utilisez Getter and Setter méthodes, plutôt qu'une propriété.
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;
}
Vous pourriez théoriquement faire ceci:
campaign.GetExtendedData().Add(something);
Mais il est beaucoup plus clair que cela ne fait pas ce que vous croyez que ça fait ™.
Si vous utilisez d'abord la base de données et que vous utilisez une sorte de générateur automatique de classe pour EF, les classes seront généralement déclarées comme étant partial
, de sorte que vous pouvez ajouter ce contenu dans un fichier séparé qui ne sera pas époustouflé vous mettez à jour vos classes à partir de votre base de données.