132 votes

Comment stocker JSON dans un champ d'entité avec EF Core ?

Je suis en train de créer une bibliothèque réutilisable à l'aide de .NET Core (visant .NETStandard 1.4) et j'utilise Entity Framework Core (je suis nouveau dans les deux cas). 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 cela 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é dans le modèle. cette propriété du modèle

Existe-t-il un moyen d'indiquer qu'il ne s'agit pas d'une relation mais qu'elle doit être stockée comme une grande chaîne de caractères ?

0 votes

Je pense que vous devriez changer le type de ExtendedData a string et ensuite stocker le JSON stringifié

2 votes

@Michael J'ai pensé à cela mais je voudrais m'assurer qu'il s'agit toujours de JSON valide.

1 votes

@Alex - Si c'est la seule préoccupation pour vérifier s'il s'agit de JSON valide, pour plus de simplicité, vous pourriez ajouter une analyse syntaxique à la méthode set de votre propriété (c'est-à-dire essayer de la désérialiser) - et lancer une InvalidDataException ou une JsonSerializationException si elle n'est pas valide.

216voto

Darren Points 7156

Je vais répondre différemment à cette question.

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 [NotMapped] propriétés consiste en fait à coupler votre modèle de domaine à votre infrastructure.

N'oubliez pas que c'est votre domaine qui est roi, et non la base de données. La base de données est juste utilisée pour stocker des parties de votre domaine.

À la place, vous pouvez utiliser l'outil EF Core HasConversion() sur la méthode 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; }
}

Je n'ai ajouté que les attributs qui intéressent le domaine - et non les détails qui intéressent la base de données. [Key] .

Mon DbContext a les caractéristiques suivantes IEntityTypeConfiguration para el 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 de sauvegarde et propriétés supplémentaires.

0 votes

Merci @Darren ! Les conversions de valeurs EF Core semblent être la voie à suivre (bien qu'elles semblent être nouvelles dans EF Core 2.1).

0 votes

Lorsque j'essaie de le faire avec JObject, j'obtiens une exception de correspondance ambiguë trouvée. Quelque chose à propos du JObject perturbe la réflexion que le noyau ef utilise.

1 votes

Si j'ajoute l'attribut [NotMapped] à la propriété JObject, puis que j'inclus la conversion, tout semble fonctionner correctement.

47voto

Alex Points 1

La réponse de @Michael m'a mis sur la voie mais je l'ai implémenté un peu différemment. J'ai fini par stocker la valeur sous forme de chaîne de caractères dans une propriété privée et par l'utiliser comme "Backing Field". La propriété ExtendedData convertit alors le JObject en chaîne de caractères lors du set et vice versa lors du 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 régler _extendedData comme champ de sauvegarde, 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, qui préconise d'utiliser les conversions de valeurs EF Core (nouvelle version d'EF Core 2.1 - qui n'existait pas au moment de cette réponse), semble être la meilleure solution pour le moment.

0 votes

Qui pourrait également être une option. Ces deux approches figurent dans la documentation de base d'EF. Le champ de sauvegarde semble plus propre mais peut être moins facile à comprendre par la suite :)

2 votes

Les deux ont la bonne approche. La seule différence est que, puisque le noyau EF prend en charge les propriétés fantômes, l'approche du champ de sauvegarde utilise une propriété fantôme et évite d'avoir une propriété supplémentaire dans votre modèle de domaine :)

0 votes

Comment l'utiliser avec jquery ?

13voto

sjclark76 Points 71

Pour ceux qui utilisent EF 2.1, il existe un petit paquetage NuGet sympa EfCoreJsonValueConverter qui rend les choses assez simples.

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();
    }
}

0 votes

Les solutions les plus simples jusqu'à présent. Voici ma mise en œuvre avec elle : stackoverflow.com/a/65781229/326904

5voto

Michael Points 647

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),

0 votes

Je pense que c'est la bonne direction - il semble que je puisse utiliser "Backing Fields" pour accomplir ceci ( docs.microsoft.com/fr/us/ef/core/modeling/backing-field ).

0 votes

Devons-nous annoter extraData comme un champ de sauvegarde ? (selon docs.microsoft.com/fr/us/ef/core/modeling/ ) Toutefois, le [BackingField(...)] ne fait pas partie des attributs habituels décrits ici : docs.microsoft.com/fr/us/ef/core/modeling/

0 votes

Cette solution a été conçue pour les anciens EF Core. Maintenant, vous pouvez annoter extraData comme champ de sauvegarde (mais vous devrez modifier légèrement la logique) ou même utiliser des propriétés fantômes ( docs.microsoft.com/fr/us/ef/core/modeling/shadow-properties ). Ou vous pouvez l'utiliser tel quel s'il fonctionne encore.

0voto

Gabriel Luci Points 6259

El commentaire de @Métoule :

Soyez prudent avec cette approche : EF Core marque une entité comme modifiée uniquement si le champ est attribué à . Ainsi, si vous utilisez person.Addresses.Add, l'entité ne sera pas signalée comme étant mise à jour ; vous devrez appeler le paramètre de propriété person.Addresses = updatedAddresses.

m'a fait adopter une approche différente pour que ce fait soit évident : utiliser Getter et 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 ça :

campaign.GetExtendedData().Add(something);

Mais il est beaucoup plus clair que ça ne fait pas ce que vous pensez que ça fait™.

Si vous utilisez la base de données en premier et que vous utilisez une sorte de générateur automatique de classes pour EF, alors les classes seront généralement déclarées en tant que partial Vous pouvez donc ajouter ces éléments dans un fichier distinct qui ne sera pas détruit lors de la prochaine mise à jour de vos classes à partir de votre base de données.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X