Entity Framework - Code First - <String> Liste Depolanamıyor


105

Böyle bir sınıf yazdım:

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"
        };
    }
}

ve

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

Kodu çalıştırdıktan sonra:

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

verilerim kaydediliyor ancak yalnızca Id. Dizeler listesine uygulanan herhangi bir tablom veya ilişkim yok .

Neyi yanlış yapıyorum? Strings yapmayı da denedim virtualama bu hiçbir şeyi değiştirmedi.

Yardımın için teşekkürler.


3
<sting> Listesinin veritabanında nasıl saklanmasını bekliyorsunuz? Bu işe yaramaz. Dize olarak değiştirin.
Wiktor Zychla

4
Bir listeniz varsa, bir varlığı işaret etmesi gerekir. EF'nin listeyi depolaması için ikinci bir tabloya ihtiyacı vardır. İkinci tabloda, listenizdeki her şeyi koyacak ve Testvarlığınıza geri dönmek için bir yabancı anahtar kullanacaktır . Öyleyse Idmülk ve MyStringmülk ile yeni bir varlık yapın , sonra bunun bir listesini yapın.
Daniel Gabriel

1
Doğru ... Doğrudan veritabanında saklanamaz, ancak Entity Framework'ün bunu kendi başına yapması için yeni bir varlık oluşturmasını umdum. Yorumlarınız için teşekkür ederim.
Paul

Yanıtlar:


160

Entity Framework, ilkel türlerin koleksiyonlarını desteklemez. Bir varlık oluşturabilir (farklı bir tabloya kaydedilecektir) veya listenizi bir dize olarak kaydetmek ve varlık gerçekleştirildikten sonra listeyi doldurmak için bir dizi dize işlemi yapabilirsiniz.


ya bir varlık bir Varlık Listesi içeriyorsa? haritalama nasıl kaydedilecek?
A_Arnold

Bağlıdır - büyük olasılıkla ayrı bir tabloya.
Pawel

json formatlı metni serileştirmeyi deneyebilir ve sonra sıkıştırıp kaydedebilir veya gerekirse şifreleyip kaydedebilir. her iki durumda da çerçevenin sizin için karmaşık tür tablo eşleştirmesini yapmasını sağlayamazsınız.
Niklas

94

EF Core 2.1+:

Emlak:

public string[] Strings { get; set; }

OnModelCreating:

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

5
EF Core için harika çözüm. Karakter dizgesine dönüştürme sorunu var gibi görünse de. Bunu şu şekilde uygulamak zorunda kaldım: .HasConversion (v => string.Join (";", v), v => v.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries));
Peter Koller

8
Bu gerçekten doğru olan tek cevap IMHO. Diğerlerinin tümü, modelinizi değiştirmenizi gerektirir ve bu, etki alanı modellerinin kalıcı cahil olması gerektiği ilkesini ihlal eder. (Ayrı kalıcılık ve etki alanı modelleri kullanıyorsanız sorun değil, ancak çok az kişi bunu yapıyor.)
Marcell Toth

2
String.Join öğesinin ilk argümanı olarak char kullanamayacağınız için düzenleme isteğimi kabul etmelisiniz ve ayrıca StringSplitOptions sağlamak istiyorsanız string.Split'in ilk argümanı olarak bir char [] sağlamalısınız.
Dominik

2
.NET Core'da yapabilirsiniz. Bu kod parçasını projelerimden birinde kullanıyorum.
Sasan

2
NET Standard
Sasan'da

55

Bu cevap @Sasan tarafından sağlananlara dayanmaktadır. ve @CAD bloke .

Yalnızca EF Core 2.1+ ile çalışır (.NET Standard uyumlu değildir) (Newtonsoft JsonConvert )

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

EF Core akıcı yapılandırmasını kullanarak, List kaldırıyoruz.

Bu kod neden çabalayabileceğiniz her şeyin mükemmel bir karışımıdır:

  • Sasn'ın orijinal cevabındaki sorun, listedeki dizeler virgül (veya sınırlayıcı olarak seçilen herhangi bir karakter) içeriyorsa, tek bir girişi birden çok girişe çevireceği için büyük bir karmaşaya dönüşeceği, ancak okunması en kolay olanıdır. en özlü.
  • CAD adamının cevabındaki sorun, çirkin olması ve modelin değiştirilmesini gerektirmesidir ki bu kötü bir tasarım uygulamasıdır (Marcell Toth'un Sasan'ın cevabı hakkındaki yorumuna bakın ). Ancak veri güvenli olan tek cevap budur.

7
bravo, bu olmalıdır muhtemelen olmak kabul cevap
Shirkan

1
Bunun .NET Framework ve EF 6'da çalışmasını isterdim, gerçekten zarif bir çözümdür.
CAD bloke

Bu harika bir çözüm. Teşekkür ederim
Marlon

Bu alanda sorgulama yapabiliyor musunuz? Girişimlerim sefil bir şekilde başarısız oldu: var result = await context.MyTable.Where(x => x.Strings.Contains("findme")).ToListAsync();hiçbir şey bulamıyor.
Nicola Iarocci

3
Kendi sorumu yanıtlamak için, dokümanları alıntılamak için : "Değer dönüştürmelerinin kullanılması, EF Core'un ifadeleri SQL'e çevirme yeteneğini etkileyebilir. Bu tür durumlar için bir uyarı günlüğe kaydedilecektir. Bu sınırlamaların kaldırılması gelecekteki bir sürüm için düşünülmektedir." - Yine de güzel olurdu.
Nicola Iarocci

43

Bunun eski bir soru olduğunu biliyorum ve Pawel doğru cevabı verdi , ben sadece bazı dizgi işlemenin nasıl yapılacağına dair bir kod örneği göstermek ve ilkel bir tipin listesi için fazladan bir sınıftan kaçınmak istedim.

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

1
Genel özellikleri kullanmak yerine neden statik yöntemler olmasın? (Ya da prosedürel programlama önyargımı gösteriyor muyum?)
Duston

@randoms 2 liste tanımlamak neden gerekli? biri mülk olarak diğeri asıl liste olarak mı? Buradaki bağlantının nasıl çalıştığını da açıklarsanız sevinirim, çünkü bu çözüm benim için iyi çalışmıyor ve buradaki bağlantıyı çözemiyorum. Teşekkürler
LiranBo

2
Dizeleri eklemek ve kaldırmak için uygulamanızda kullanacağınız Strings ve virgülle ayrılmış liste olarak db'ye kaydedilecek değer olan StringsAsString adıyla ilişkili iki ortak özelliğe sahip bir özel liste vardır. Yine de ne sorduğunuzdan emin değilim, bağlama, iki genel özelliği birbirine bağlayan özel liste _strings.
randoms

1
Lütfen bu cevabın ,dizelerde virgülden kaçmadığını unutmayın. Listedeki bir dize bir veya daha fazla ,(virgül) içeriyorsa , dize birden çok dizeye bölünür.
17:32

2
In string.Joinvirgül (bir dize için) çift tırnak değil, (char için) tek tırnak ile çevrili olmalıdır. Bkz. Msdn.microsoft.com/en-us/library/57a79xd0(v=vs.110).aspx
Michael Brandon Morris

29

JSON.NETKurtarmaya .

Veritabanında kalması için JSON olarak serileştirirsiniz ve .NET koleksiyonunu yeniden oluşturmak için serisini kaldırırsınız. Bu, Entity Framework 6 ve SQLite ile beklediğimden daha iyi performans gösteriyor gibi görünüyor. İstediğini biliyorumList<string> ama işte gayet iyi çalışan daha karmaşık bir koleksiyon örneği.

Kalıcı özelliği ile etiketledim, [Obsolete]böylece normal kodlama sürecinde "aradığınız özellik bu değil" benim için çok açık olacaktı. "Emlak" özelliği ile etiketlenmiştir[NotMapped] Entity çerçevesi onu yok sayar.

(ilgisiz tanjant): Aynı şeyi daha karmaşık türler için de yapabilirsiniz, ancak kendinize sormanız gerekir, bu nesnenin özelliklerini sorgulamayı kendiniz için çok mu zorlaştırdınız? (evet, benim durumumda).

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

Bu çözümü oldukça çirkin buluyorum, ama aslında tek mantıklı çözüm bu. Listeye hangi karakteri kullanarak katılmayı ve sonra geri bölmeyi teklif eden tüm seçenekler, eğer bölme karakteri dizelere dahil edilmişse çılgın bir karmaşaya dönüşebilir. Json çok daha aklı başında olmalı.
Mathieu VIALES

1
Sonunda, diğerinin güçlü noktalarını kullanarak her bir cevap problemini (çirkinlik / veri güvenliği) düzeltmek için bu ve diğerinin "birleşmesi" olan bir cevap verdim .
Mathieu Viales

13

Sadece basitleştirmek için -

Varlık çerçevesi temelleri desteklemez. Ya onu sarmak için bir sınıf oluşturursunuz ya da listeyi bir dize olarak biçimlendirmek için başka bir özellik eklersiniz:

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

1
Bu, bir liste öğesinin bir dizgi içerememesi durumundadır. Aksi takdirde, ondan kaçmanız gerekir. Veya daha karmaşık durumlar için listeyi serileştirmek / seriyi kaldırmak için.
Adam Tal

3
Ayrıca, ICollection mülkünde [NotMapped] kullanmayı unutmayın
Ben Petersen

7

Tabii ki Pawel doğru cevabı verdi . Ancak bu yazıda EF 6+ 'den beri özel mülkleri kaydetmenin mümkün olduğunu buldum . Bu yüzden bu kodu tercih ederim, çünkü Dizeleri yanlış bir şekilde kaydedemezsiniz.

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"
        };
    }
}

6
Ya dize virgül içeriyorsa?
Chalky

4
Bunu bu şekilde yapmanızı tavsiye etmem. StringsAsStringsyalnızca Strings referans değiştirildiğinde güncellenecektir ve örneğinizde gerçekleşen tek zaman atama zamanıdır. Ekleme veya öğe kaldırıyorsunuz Stringsolacak görevden sonra listede değil güncelleştirmek StringsAsStringsdestek değişkeni. Bunu uygulamanın doğru yolu, tam tersi değil StringsAsStrings, Stringslistenin bir görünümü olarak ortaya koymaktır . Değerleri mülkün geterişimcisinde birleştirin StringsAsStringsve bunları seterişimcide bölün .
jduncanator

Özel özellikler eklemekten kaçınmak için (yan etkisiz değildir), serileştirilmiş özelliğin ayarlayıcısını özel yapın. jduncanator elbette haklıdır: eğer liste işlemlerini yakalayamazsanız (bir ObservableCollection kullanın?), değişiklikler EF tarafından fark edilmeyecektir.
Leonidas

@Jduncanator'ın belirttiği gibi, Listede bir değişiklik yapıldığında bu çözüm işe yaramıyor (örneğin MVVM'de bağlayıcı)
Ihab Hajj

7

Biraz verdiği @Mathieu Viales bireyin cevabı , burada böylece Newtonsoft.Json bağımlılığını ortadan kaldırarak yeni System.Text.Json seri hale kullanarak .NET Standart uyumlu snippet'ine.

using System.Text.Json;

builder.Entity<YourEntity>().Property(p => p.Strings)
    .HasConversion(
        v => JsonSerializer.Serialize(v, default),
        v => JsonSerializer.Deserialize<List<string>>(v, default));

Her ikisinde de ikinci bağımsız değişken Serialize()ve Deserialize()genellikle isteğe bağlı olsa da, bir hata alacağınızı unutmayın:

Bir ifade ağacı, isteğe bağlı bağımsız değişkenler kullanan bir çağrı veya çağrı içeremez

Açıkça bunu her biri için varsayılan (null) olarak ayarlamak bunu temizler.


3

ScalarCollectionBir diziyi sınırlayan ve bazı manipülasyon seçenekleri sağlayan bu kabı kullanabilirsiniz ( Gist ) :

Kullanım:

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

Kod:

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

8
biraz fazla tasarlanmış mı ?!
Falco Alexander

1
@FalcoAlexander Yazımı güncelledim ... Belki biraz ayrıntılı ama işi yapıyor. NET462Uygun ortamla değiştirdiğinizden veya ona eklediğinizden emin olun .
Shimmy Weitzhandler

1
Bunu bir araya getirme çabası için +1. Çözüm, bir dizi
dizgiyi

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.