इकाई फ्रेमवर्क - कोड प्रथम - स्टोर सूची <स्ट्रिंग> नहीं कर सकता


106

मैंने ऐसी कक्षा लिखी:

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

तथा

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

रन कोड के बाद:

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

मेरा डेटा सहेजा जा रहा है लेकिन सिर्फ Id। मेरे पास कोई सूची नहीं है और न ही रिश्तों को स्ट्रिंग्स सूची में लागू किया गया है ।

मैं क्या गलत कर रहा हूं? मैंने स्ट्रिंग्स बनाने की भी कोशिश की virtualलेकिन यह कुछ भी नहीं बदला।

आपके सहयोग के लिए धन्यवाद।


3
आप कैसे उम्मीद करते हैं सूची <स्टिंग> डीबी में संग्रहीत है? यह काम नहीं करेगा। इसे स्ट्रिंग में बदलें।
विकिटोर ज़िकला

4
यदि आपके पास एक सूची है, तो उसे कुछ इकाई को इंगित करना होगा। ईएफ को सूची संग्रहीत करने के लिए, इसे दूसरी तालिका की आवश्यकता है। दूसरी तालिका में यह आपकी सूची से सब कुछ डाल देगा, और आपकी Testइकाई पर वापस इंगित करने के लिए एक विदेशी कुंजी का उपयोग करेगा । इसलिए Idसंपत्ति और MyStringसंपत्ति के साथ एक नई इकाई बनाएं, फिर उस की एक सूची बनाएं।
डैनियल गैब्रियल

1
ठीक है ... इसे सीधे db में संग्रहीत नहीं किया जा सकता है, लेकिन मुझे आशा है कि Entity फ्रेमवर्क अपने आप ही ऐसा करने के लिए नई इकाई का निर्माण करेगा। आपकी टिप्पणीयों के लिए धन्यवाद।
पॉल

जवाबों:


161

एंटिटी फ्रेमवर्क आदिम प्रकारों के संग्रह का समर्थन नहीं करता है। आप या तो एक इकाई बना सकते हैं (जिसे एक अलग तालिका में सहेजा जाएगा) या अपनी स्ट्रिंग को एक स्ट्रिंग के रूप में सहेजने और इकाई के भौतिक होने के बाद सूची को पॉप्युलेट करने के लिए कुछ स्ट्रिंग प्रसंस्करण करें।


क्या होगा यदि किसी संस्था में संस्थाओं की सूची है? मैपिंग को कैसे बचाया जाएगा?
a_Annold

निर्भर करता है - एक अलग तालिका के लिए सबसे अधिक संभावना है।
पावेल

क्रमबद्ध करने और फिर संपीड़ित स्वरूपित पाठ, या एन्क्रिप्ट करने और इसे बचाने के लिए और यदि आवश्यक हो तो इसे बचाने की कोशिश कर सकते हैं। किसी भी तरह से आपके पास ढांचा नहीं है, आपके लिए जटिल प्रकार की टेबल मैपिंग है।
निकोलस

89

EF Core 2.1+:

संपत्ति:

public string[] Strings { get; set; }

OnModelCreating:

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

5
ईएफ कोर के लिए महान समाधान। हालांकि ऐसा लगता है कि एक मुद्दा व्हाइट चेंज टू स्ट्रिंग रूपांतरण है। मुझे इसे इस तरह से लागू करना था: .HasConversion (v => string.Join (";", v), v => v.Split (नया char [] {';'}; StringSplitOptions.emoveEmptyEntries));
पीटर कोल्लर

8
यह वास्तव में सही सही IMHO है। अन्य सभी को आपको अपना मॉडल बदलने की आवश्यकता है, और यह इस सिद्धांत का उल्लंघन करता है कि डोमेन मॉडल अज्ञानता होना चाहिए। (यह ठीक है अगर आप अलग दृढ़ता और डोमेन मॉडल का उपयोग कर रहे हैं, लेकिन कुछ लोग वास्तव में ऐसा करते हैं।)
मार्सेल टोह

2
आपको मेरा संपादन अनुरोध स्वीकार करना चाहिए क्योंकि आप स्ट्रिंग के पहले तर्क के रूप में चार का उपयोग नहीं कर सकते हैं। जाइए और आपको स्ट्रिंग के पहले तर्क के रूप में एक चार [] प्रदान करना होगा।
डोमिनिक

2
.NET Core में आप कर सकते हैं। मैं अपनी एक परियोजना में कोड के इस सटीक टुकड़े का उपयोग कर रहा हूं।
सासन

2
.NET मानक में उपलब्ध नहीं है
सासन

54

यह उत्तर @Sasan और @CAD ब्लोक द्वारा उपलब्ध कराए गए लोगों पर आधारित है ।

केवल EF Core 2.1+ के साथ काम करता है (.NET मानक संगत नहीं है) (न्यूटनसॉफ्ट JsonConvert)

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

EF कोर धाराप्रवाह विन्यास का उपयोग हम सीरियल / ListJSON से / के लिए deserialize ।

यह कोड उन सभी चीज़ों का सही मिश्रण है, जिनके लिए आप प्रयास कर सकते हैं:

  • सासन के मूल उत्तर के साथ समस्या यह है कि यह एक बड़ी गड़बड़ी में बदल जाएगा यदि सूची में तार में अल्पविराम (या किसी भी चरित्र को सीमांकित के रूप में चुना गया है) क्योंकि यह एकल प्रविष्टि को कई प्रविष्टियों में बदल देगा लेकिन यह पढ़ने में सबसे आसान है सबसे संक्षिप्त।
  • सीएडी ब्लोक के उत्तर के साथ समस्या यह है कि यह बदसूरत है और मॉडल को बदलने की आवश्यकता है जो एक खराब डिजाइन अभ्यास है (देखें सासन के जवाब पर मार्सेल टोथ की टिप्पणी )। लेकिन यह एकमात्र उत्तर है जो डेटा-सुरक्षित है।

7
ब्रावो, यह संभवत: स्वीकृत उत्तर होना चाहिए
शिरकन

1
काश यह .NET फ्रेमवर्क और ईएफ 6 में काम करता है, यह एक बहुत ही सुंदर समाधान है।
सीएडी

यह एक अद्भुत उपाय है। धन्यवाद
मार्लोन

क्या आप उस फ़ील्ड पर क्वेरी करने में सक्षम हैं? मेरे प्रयास बुरी तरह विफल रहे: var result = await context.MyTable.Where(x => x.Strings.Contains("findme")).ToListAsync();कुछ नहीं मिला।
निकोला इरोकी

3
मेरे स्वयं के प्रश्न का उत्तर देने के लिए, डॉक्स के हवाले से : "मूल्य रूपांतरणों का उपयोग अभिव्यक्ति को एसक्यूएल में अनुवाद करने के लिए ईएफ कोर की क्षमता को प्रभावित कर सकता है। ऐसे मामलों के लिए एक चेतावनी लॉग की जाएगी। इन सीमाओं को हटाने को भविष्य के रिलीज के लिए माना जा रहा है।" - हालांकि अभी भी अच्छा होगा।
निकोला इरोकी

44

मुझे पता है कि यह एक पुराना सवाल है, और पावेल ने सही उत्तर दिया है , मैं बस कुछ स्ट्रिंग प्रसंस्करण कैसे करना है, और एक आदिम प्रकार की सूची के लिए एक अतिरिक्त वर्ग से बचने का एक कोड उदाहरण दिखाना चाहता था।

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
सार्वजनिक गुणों का उपयोग करने के बजाय स्थिर तरीके क्यों नहीं? (या मैं अपना प्रक्रियात्मक प्रोग्रामिंग पूर्वाग्रह दिखा रहा हूँ?)
डस्टन १15

@ कैंडम 2 सूचियों को परिभाषित करना क्यों आवश्यक है? एक संपत्ति के रूप में और एक वास्तविक सूची के रूप में? मैं सराहना करूंगा यदि आप यह भी बता सकते हैं कि यहां बाध्यकारी कैसे काम करता है, क्योंकि यह समाधान मेरे लिए अच्छी तरह से काम नहीं कर रहा है, और मैं यहां बाध्यकारी का पता नहीं लगा सकता। धन्यवाद
LiranBo

2
एक निजी सूची है, जिसमें दो सार्वजनिक गुण जुड़े हैं, स्ट्रिंग्स, जिसे आप अपने एप्लिकेशन में स्ट्रिंग्स को जोड़ने और हटाने के लिए उपयोग करेंगे, और स्ट्रिंग्सएस्ट्रिंग जो कि वह मान है जो db में सहेजा जाएगा, अल्पविराम से अलग सूची के रूप में। मुझे वास्तव में यकीन नहीं है कि आप क्या पूछ रहे हैं हालांकि, बंधन निजी सूची _strings है, जो दो सार्वजनिक गुणों को एक साथ जोड़ता है।
randoms

1
कृपया ध्यान रखें कि यह उत्तर ,तार में (कॉमा) नहीं बचता है । यदि सूची में एक स्ट्रिंग में एक या अधिक ,(अल्पविराम) होता है, तो स्ट्रिंग को कई तारों में विभाजित किया जाता है।
जोगेग

2
में string.Joinअल्पविराम दोहरे उद्धरण चिह्नों (एक स्ट्रिंग के लिए), नहीं एकल उद्धरण (एक चार के लिए) से घिरा हुआ होना चाहिए। देखें msdn.microsoft.com/en-us/library/57a79xd0(v=vs.110).aspx
माइकल ब्रैंडन मॉरिस

29

बचाव के लिए JSON.NET

आप इसे डेटाबेस में जारी रखने के लिए JSON पर अनुक्रमित करते हैं और .NET संग्रह के पुनर्गठन के लिए इसका वर्णन करते हैं। ऐसा लगता है कि मुझे इसकी अपेक्षा बेहतर प्रदर्शन करने की आवश्यकता है। मुझे पता है कि आपने इसके लिए कहा था List<string>लेकिन यहां और भी जटिल संग्रह का एक उदाहरण है जो ठीक काम करता है।

मैंने लगातार संपत्ति को टैग किया, [Obsolete]इसलिए मेरे लिए यह बहुत स्पष्ट होगा कि "यह वह संपत्ति नहीं है जिसे आप खोज रहे हैं" कोडिंग के सामान्य पाठ्यक्रम में। "रियल" प्रॉपर्टी को टैग किया जाता है [NotMapped]ताकि एंटिटी फ्रेमवर्क इसकी अनदेखी करे।

(असंबंधित स्पर्शरेखा): आप इसे अधिक जटिल प्रकारों के साथ कर सकते हैं लेकिन आपको खुद से यह पूछने की आवश्यकता है कि क्या आपने उस वस्तु के गुणों को अपने लिए बहुत कठिन बना दिया है? (हाँ, मेरे मामले में)।

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

मुझे यह समाधान काफी बदसूरत लगता है, लेकिन यह वास्तव में एकमात्र समझदार है। जो भी चरित्र का उपयोग कर सूची में शामिल होने और फिर इसे विभाजित करने की पेशकश करने वाले सभी विकल्प एक जंगली गड़बड़ में बदल सकते हैं यदि विभाजन चरित्र को तार में शामिल किया जाता है। Json को और अधिक समझदार होना चाहिए।
मैथ्यू विजल्स

1
मैंने एक उत्तर दिया जो इस एक का "मर्ज" है और एक दूसरे के मजबूत बिंदुओं का उपयोग करके प्रत्येक उत्तर समस्या (कुरूपता / डेटा-सुरक्षा) को ठीक करने के लिए एक है।
मैथ्यू विजल्स

13

बस सरलीकृत करने के लिए -

एंटिटी फ्रेमवर्क आदिमताओं का समर्थन नहीं करता है। आप या तो इसे लपेटने के लिए एक वर्ग बनाते हैं या एक स्ट्रिंग के रूप में सूची को प्रारूपित करने के लिए एक और संपत्ति जोड़ते हैं:

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

1
ऐसा तब होता है जब सूची आइटम में स्ट्रिंग नहीं हो सकती है। अन्यथा, आपको इससे बचने की आवश्यकता होगी। या अधिक जटिल स्थितियों के लिए सूची को अनुक्रमित / डिस्क्राइब करने के लिए।
एडम ताल

3
इसके अलावा, ICOLlection संपत्ति पर [NotMapped] का उपयोग करना न भूलें
बेन पीटरसन

7

बेशक पावेल ने सही जवाब दिया है । लेकिन मैंने इस पोस्ट में पाया कि EF 6+ के बाद से निजी संपत्तियों को बचाना संभव है। इसलिए मैं इस कोड को पसंद करूंगा, क्योंकि आप स्ट्रिंग्स को गलत तरीके से सहेजने में सक्षम नहीं हैं।

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
यदि स्ट्रिंग में अल्पविराम होता है तो क्या होगा?
चलि

4
मैं इसे इस तरह से करने की सलाह नहीं दूंगा। StringsAsStringsकेवल तभी बदला जाएगा जब Strings संदर्भ बदल दिया जाता है, और आपके उदाहरण में एकमात्र समय असाइनमेंट पर होता है। Stringsअसाइनमेंट के बाद आपकी सूची से आइटम जोड़ना या निकालना बैकिंग चर को अपडेट नहीं करेगा StringsAsStrings। इसे लागू करने का उचित तरीका सूची के StringsAsStringsदृश्य के रूप में प्रकट करना होगा Strings, बजाय अन्य तरीके के। मानों को एक साथ शामिल हों में getकी एक्सेसर StringsAsStringsसंपत्ति, और उन में विभाजित setएक्सेसर।
jduncanator

निजी संपत्तियों को जोड़ने से बचने के लिए (जो साइड इफेक्ट फ्री नहीं है) धारावाहिक संपत्ति के सेटर को निजी बनाएं। jduncanator निश्चित रूप से सही है: यदि आप सूची में गड़बड़ी नहीं पकड़ते हैं (एक ObservableCollection का उपयोग करें?), परिवर्तन EF द्वारा नहीं देखा जाएगा।
लियोनिडास

जैसा कि @jduncanator ने उल्लेख किया है कि यह समाधान तब काम नहीं करता है जब सूची में संशोधन किया जाता है (उदाहरण के लिए MVVM में बाइंडिंग)
इहाब हज

7

@Mathieu Viales के उत्तर को थोड़ा सा घुमाते हुए , यहां एक .NET मानक संगत स्निपेट का उपयोग करके नया System.Text.Json क्रमबद्ध किया गया है, जो न्यूटनसॉफ्ट पर निर्भरता को समाप्त करता है। Json।

using System.Text.Json;

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

ध्यान दें कि दोनों में दूसरा तर्क Serialize()और Deserialize()आमतौर पर वैकल्पिक है, आपको एक त्रुटि मिलेगी:

एक अभिव्यक्ति ट्री में एक कॉल या आह्वान नहीं हो सकता है जो वैकल्पिक तर्कों का उपयोग करता है

स्पष्ट रूप से सेट करने के लिए डिफ़ॉल्ट (अशक्त) कि प्रत्येक क्लीयर के लिए।


3

आप इस ScalarCollectionकंटेनर का उपयोग कर सकते हैं जो एक सरणी को परिभाषित करता है और कुछ हेरफेर विकल्प ( Gist ) प्रदान करता है :

उपयोग:

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

कोड:

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
इंजीनियर पर एक सा लग रहा है ?!
फाल्को अलेक्जेंडर

1
@FalcoAlexander मैंने अपनी पोस्ट को अपडेट किया है ... शायद थोड़ा वर्बोज़ है लेकिन काम करता है। सुनिश्चित करें कि आप NET462उपयुक्त वातावरण से प्रतिस्थापित करते हैं या इसे इसमें जोड़ते हैं।
शिम्मी वेइटहैंडलर

1
इसे एक साथ रखने के प्रयास के लिए +1। समाधान स्ट्रिंग्स की एक सरणी के भंडारण के लिए थोड़ा सा ओवरकिल है :)
गीता
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.