web-dev-qa-db-fra.com

Entity Framework - Code d'abord - Impossible de stocker la liste <String>

J'ai écrit un tel cours:

class Test
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    [Required]
    public List<String> Strings { get; set; }

    public Test()
    {
        Strings = new List<string>
        {
            "test",
            "test2",
            "test3",
            "test4"
        };
    }
}

et

internal class DataContext : DbContext
{
    public DbSet<Test> Tests { get; set; }
}

Après le code d'exécution:

var db = new DataContext();
db.Tests.Add(new Test());
db.SaveChanges();

mes données sont en train d'être sauvegardées mais juste le Id. Je n'ai aucune table ni relation s'appliquant à la liste Chaînes.

Qu'est-ce que je fais mal? J'ai aussi essayé de faire Chaînesvirtual mais cela n'a rien changé.

Merci de votre aide.

78
Paul

Entity Framework ne prend pas en charge les collections de types primitifs. Vous pouvez créer une entité (qui sera enregistrée dans une table différente) ou effectuer un traitement de chaîne pour enregistrer votre liste en tant que chaîne et la remplir une fois que l'entité est matérialisée.

144
Pawel

Je sais que c'est une vieille question et Pawel a donné la bonne réponse , je voulais juste montrer un exemple de code montrant comment effectuer un traitement de chaîne et éviter une classe supplémentaire pour la liste d'une primitive type.

public class Test
{
    public Test()
    {
        _strings = new List<string>
        {
            "test",
            "test2",
            "test3",
            "test4"
        };
    }

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    private List<String> _strings { get; set; }

    public List<string> Strings
    {
        get { return _strings; }
        set { _strings = value; }
    }

    [Required]
    public string StringsAsString
    {
        get { return String.Join(',', _strings); }
        set { _strings = value.Split(',').ToList(); }
    }
}
39
randoms

EF Core 2.1+:

Propriété:

public string[] Strings { get; set; }

OnModelCreating:

modelBuilder.Entity<YourEntity>()
            .Property(e => e.Strings)
            .HasConversion(
                v => string.Join(',', v),
                v => v.Split(',', StringSplitOptions.RemoveEmptyEntries));
38
Sasan

JSON.NET à la rescousse.

Vous le sérienez en JSON pour qu'il persiste dans la base de données et vous le désérialisez pour reconstituer la collection .NET. Cela semble mieux fonctionner que prévu avec Entity Framework 6 et SQLite. Je sais que vous avez demandé List<string>, Mais voici un exemple d'une collection encore plus complexe qui fonctionne parfaitement.

J'ai étiqueté la propriété persistante avec [Obsolete], Il serait donc très évident pour moi que "ce n'est pas la propriété que vous recherchez" dans le cours normal du codage. La propriété "real" est étiquetée avec [NotMapped], Donc le cadre Entity l'ignore.

(tangente indépendante): Vous pouvez faire la même chose avec des types plus complexes, mais vous devez vous demander si vous avez juste rendu la recherche des propriétés de cet objet trop difficile pour vous-même. (oui, dans mon cas).

using Newtonsoft.Json;
....
[NotMapped]
public Dictionary<string, string> MetaData { get; set; } = new Dictionary<string, string>();

/// <summary> <see cref="MetaData"/> for database persistence. </summary>
[Obsolete("Only for Persistence by EntityFramework")]
public string MetaDataJsonForDb
{
    get
    {
        return MetaData == null || !MetaData.Any()
                   ? null
                   : JsonConvert.SerializeObject(MetaData);
    }

    set
    {
        if (string.IsNullOrWhiteSpace(value))
           MetaData.Clear();
        else
           MetaData = JsonConvert.DeserializeObject<Dictionary<string, string>>(value);
    }
}
26
CAD bloke

Juste pour simplifier -

Entity Framework ne supporte pas les primitives. Vous pouvez créer une classe pour l'envelopper ou ajouter une autre propriété pour formater la liste en tant que chaîne:

public ICollection<string> List { get; set; }
public string ListString
{
    get { return string.Join(",", List); }
    set { List = value.Split(',').ToList(); }
}
11
Adam Tal

Cette réponse est basée sur celles fournies par @ Sasan et @ CAD CAD .

Fonctionne uniquement avec EF Core 2.1+ (non compatible avec .NET Standard) (Newtonsoft JsonConvert)

builder.Entity<YourEntity>().Property(p => p.Strings)
    .HasConversion(
        v => JsonConvert.SerializeObject(v),
        v => JsonConvert.DeserializeObject<List<string>>(v));

En utilisant la configuration fluide d’EF Core, nous sérialisons/désérialisons le List vers/depuis JSON.

Pourquoi ce code est le mélange parfait de tout ce que vous pourriez rechercher:

  • Le problème avec la réponse initiale de Sasn est que cela va se transformer en désordre si les chaînes de la liste contiennent des virgules (ou tout autre caractère choisi comme délimiteur), car elles transformeront une seule entrée en plusieurs entrées, mais c'est la plus facile à lire et à utiliser. le plus concis.
  • Le problème avec CAD la réponse de bloke est que c'est moche et nécessite de modifier le modèle, ce qui est une mauvaise pratique de conception (voir le commentaire de Marcell Toth sur réponse de Sasan ). Mais c’est la seule réponse qui soit sécurisée.
8
Mathieu VIALES

Bien sûr, Pawel a donné la bonne réponse . Mais j’ai trouvé dans ce post que, depuis EF 6+, il est possible de sauvegarder des propriétés privées. Je préférerais donc ce code, car vous ne pouvez pas enregistrer les chaînes de manière erronée.

public class Test
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Column]
    [Required]
    private String StringsAsStrings { get; set; }

    public List<String> Strings
    {
        get { return StringsAsStrings.Split(',').ToList(); }
        set
        {
            StringsAsStrings = String.Join(",", value);
        }
    }
    public Test()
    {
        Strings = new List<string>
        {
            "test",
            "test2",
            "test3",
            "test4"
        };
    }
}
8
Plumpssack

Vous pouvez utiliser ce conteneur ScalarCollection qui confine un tableau et fournit des options de manipulation ( Gist ):

Usage:

public class Person
{
    public int Id { get; set; }
    //will be stored in database as single string.
    public SaclarStringCollection Phones { get; set; } = new ScalarStringCollection();
}

Code:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace System.Collections.Specialized
{
#if NET462
  [ComplexType]
#endif
  public abstract class ScalarCollectionBase<T> :
#if NET462
    Collection<T>,
#else
    ObservableCollection<T>
#endif
  {
    public virtual string Separator { get; } = "\n";
    public virtual string ReplacementChar { get; } = " ";
    public ScalarCollectionBase(params T[] values)
    {
      if (values != null)
        foreach (var item in Items)
          Items.Add(item);
    }

#if NET462
    [Browsable(false)]
#endif
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("Not to be used directly by user, use Items property instead.")]
    public string Data
    {
      get
      {
        var data = Items.Select(item => Serialize(item)
          .Replace(Separator, ReplacementChar.ToString()));
        return string.Join(Separator, data.Where(s => s?.Length > 0));
      }
      set
      {
        Items.Clear();
        if (string.IsNullOrWhiteSpace(value))
          return;

        foreach (var item in value
            .Split(new[] { Separator }, 
              StringSplitOptions.RemoveEmptyEntries).Select(item => Deserialize(item)))
          Items.Add(item);
      }
    }

    public void AddRange(params T[] items)
    {
      if (items != null)
        foreach (var item in items)
          Add(item);
    }

    protected abstract string Serialize(T item);
    protected abstract T Deserialize(string item);
  }

  public class ScalarStringCollection : ScalarCollectionBase<string>
  {
    protected override string Deserialize(string item) => item;
    protected override string Serialize(string item) => item;
  }

  public class ScalarCollection<T> : ScalarCollectionBase<T>
    where T : IConvertible
  {
    protected override T Deserialize(string item) =>
      (T)Convert.ChangeType(item, typeof(T));
    protected override string Serialize(T item) => Convert.ToString(item);
  }
}
2
Shimmy