Khung thực thể - Mã đầu tiên - Danh sách không thể lưu trữ <Chuỗi>


106

Tôi đã viết lớp học như vậy:

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

Sau khi chạy mã:

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

dữ liệu của tôi đang được lưu nhưng chỉ là Id. Tôi không có bất kỳ bảng cũng như mối quan hệ nào áp dụng cho danh sách Chuỗi .

Tôi đang làm gì sai? Tôi cũng đã thử tạo Strings virtual nhưng nó không thay đổi được gì.

Cảm ơn sự giúp đỡ của bạn.


3
Làm thế nào để bạn mong đợi Danh sách <sting> được lưu trữ trong db? Điều đó sẽ không hoạt động. Thay đổi nó thành chuỗi.
Wiktor Zychla

4
Nếu bạn có một danh sách, nó phải trỏ đến một thực thể nào đó. Để EF lưu trữ danh sách, nó cần một bảng thứ hai. Trong bảng thứ hai, nó sẽ đưa mọi thứ từ danh sách của bạn và sử dụng khóa ngoại để trỏ trở lại Testthực thể của bạn . Vì vậy, hãy tạo một thực thể mới với thuộc Idtính và thuộc MyStringtính, sau đó tạo một danh sách về điều đó.
Daniel Gabriel

1
Đúng ... Nó không thể được lưu trữ trực tiếp trong db nhưng tôi hy vọng Entity Framework tạo ra thực thể mới để tự làm điều đó. Cảm ơn bạn đã bình luận của bạn.
Paul

Câu trả lời:


161

Entity Framework không hỗ trợ tập hợp các kiểu nguyên thủy. Bạn có thể tạo một thực thể (sẽ được lưu vào một bảng khác) hoặc thực hiện một số xử lý chuỗi để lưu danh sách của bạn dưới dạng một chuỗi và điền vào danh sách sau khi thực thể được thực thể hóa.


điều gì sẽ xảy ra nếu một thực thể chứa Danh sách các thực thể? ánh xạ sẽ được lưu như thế nào?
A_Arnold

Phụ thuộc - rất có thể vào một bảng riêng biệt.
Pawel

có thể thử tuần tự hóa rồi nén và lưu văn bản được định dạng json hoặc mã hóa và lưu nó nếu cần. Dù theo cách nào thì bạn cũng không thể có khung thực hiện ánh xạ bảng kiểu phức tạp cho bạn.
Niklas

89

EF Core 2.1+:

Bất động sản:

public string[] Strings { get; set; }

OnModelCreating:

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

5
Giải pháp tuyệt vời cho EF Core. Mặc dù nó có vẻ có vấn đề khi chuyển đổi char thành chuỗi. Tôi đã phải triển khai nó như sau: .HasConversion (v => string.Join (";", v), v => v.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries));
Peter Koller

8
Đây là câu trả lời thực sự đúng duy nhất IMHO. Tất cả những thứ khác đều yêu cầu bạn thay đổi mô hình của mình và điều đó vi phạm nguyên tắc rằng các mô hình miền phải kiên trì không biết. (Nó là tốt nếu bạn đang sử dụng bền bỉ và miền mô hình riêng biệt, nhưng rất ít người thực sự làm điều đó.)
Marcell Toth

2
Bạn nên chấp nhận yêu cầu chỉnh sửa của tôi vì bạn không thể sử dụng char làm đối số đầu tiên của string.Join và bạn phải cung cấp char [] làm đối số đầu tiên của string.Split nếu bạn cũng muốn cung cấp StringSplitOptions.
Dominik

2
Trong .NET Core, bạn có thể. Tôi đang sử dụng đoạn mã chính xác này trong một trong những dự án của mình.
Sasan

2
Không có sẵn trong .NET Standard
Sasan

54

Câu trả lời này dựa trên những câu trả lời được cung cấp bởi @Sasan@CAD bloke .

Chỉ hoạt động với EF Core 2.1+ (không tương thích .NET Standard) (Newtonsoft JsonConvert)

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

Sử dụng cấu hình thông thạo EF Core, chúng tôi tuần tự hóa / giải mã hóa Listđến / từ JSON.

Tại sao mã này là sự kết hợp hoàn hảo của mọi thứ bạn có thể cố gắng:

  • Vấn đề với câu trả lời ban đầu của Sasn là nó sẽ trở thành một mớ hỗn độn lớn nếu các chuỗi trong danh sách chứa dấu phẩy (hoặc bất kỳ ký tự nào được chọn làm dấu phân cách) vì nó sẽ biến một mục nhập thành nhiều mục nhập nhưng dễ đọc nhất và ngắn gọn nhất.
  • Vấn đề với câu trả lời của CAD bloke là nó xấu và yêu cầu thay đổi mô hình, đây là một thực tiễn thiết kế tồi (xem nhận xét của Marcell Toth về câu trả lời của Sasan ). Nhưng đó là câu trả lời duy nhất là an toàn về dữ liệu.

7
bravo, điều này sẽ có thể là câu trả lời được chấp nhận
Shirkan

1
Tôi ước điều này hoạt động trong .NET Framework & EF 6, đó là một giải pháp thực sự thanh lịch.
CAD bloke

Đây là một giải pháp tuyệt vời. Cảm ơn bạn
Marlon

Bạn có khả năng truy vấn về lĩnh vực đó không? Những nỗ lực của tôi đã thất bại thảm hại: var result = await context.MyTable.Where(x => x.Strings.Contains("findme")).ToListAsync();không tìm thấy gì cả.
Nicola Iarocci

3
Để trả lời câu hỏi của riêng tôi, trích dẫn tài liệu : "Việc sử dụng chuyển đổi giá trị có thể ảnh hưởng đến khả năng dịch các biểu thức sang SQL của EF Core. Một cảnh báo sẽ được ghi lại cho những trường hợp như vậy. Việc loại bỏ các giới hạn này đang được xem xét cho bản phát hành trong tương lai." - Vẫn sẽ tốt đẹp.
Nicola Iarocci

44

Tôi biết đây là một câu hỏi cũ và Pawel đã đưa ra câu trả lời chính xác , tôi chỉ muốn hiển thị một ví dụ mã về cách thực hiện một số xử lý chuỗi và tránh một lớp bổ sung cho danh sách kiểu nguyên thủy.

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
Tại sao không sử dụng các phương thức tĩnh thay vì sử dụng các thuộc tính chung? (Hay tôi đang thể hiện sự thiên vị về lập trình thủ tục của mình?)
Duston

@randoms tại sao cần xác định 2 danh sách? một dưới dạng tài sản và một là danh sách thực tế? Tôi sẽ đánh giá cao nếu bạn cũng có thể giải thích cách hoạt động của ràng buộc ở đây, bởi vì giải pháp này không hoạt động tốt đối với tôi và tôi không thể tìm ra ràng buộc ở đây. Cảm ơn
LiranBo

2
có một danh sách riêng tư, có hai thuộc tính công khai được liên kết, Strings, mà bạn sẽ sử dụng trong ứng dụng của mình để thêm và xóa các chuỗi, và StringsAsString là giá trị sẽ được lưu vào db, dưới dạng danh sách được phân tách bằng dấu phẩy. Tuy nhiên, tôi không thực sự chắc chắn những gì bạn đang hỏi, ràng buộc là _strings danh sách riêng tư, kết nối hai thuộc tính chung với nhau.
randoms

1
Xin lưu ý rằng câu trả lời này không thoát ,(dấu phẩy) trong chuỗi. Nếu một chuỗi trong danh sách chứa một hoặc nhiều ,(dấu phẩy) thì chuỗi đó được chia thành nhiều chuỗi.
Jogge

2
Trong string.Joindấu phẩy phải được bao quanh bởi dấu ngoặc kép (đối với một chuỗi), không phải dấu nháy đơn (đối với ký tự). Xem msdn.microsoft.com/en-us/library/57a79xd0(v=vs.110).aspx
Michael Brandon Morris

29

JSON.NET để giải cứu.

Bạn tuần tự hóa nó thành JSON để tồn tại trong Cơ sở dữ liệu và Deserialize nó để tạo lại bộ sưu tập .NET. Điều này dường như hoạt động tốt hơn tôi mong đợi với Entity Framework 6 & SQLite. Tôi biết bạn đã yêu cầu List<string>nhưng đây là một ví dụ về một bộ sưu tập thậm chí còn phức tạp hơn nhưng hoạt động tốt.

Tôi đã gắn thẻ thuộc tính vẫn tồn tại với [Obsolete]vì vậy tôi sẽ thấy rất rõ ràng rằng "đây không phải là thuộc tính bạn đang tìm kiếm" trong quá trình viết mã thông thường. Thuộc tính "thực" được gắn thẻ [NotMapped]nên khung thực thể bỏ qua nó.

(tiếp tuyến không liên quan): Bạn có thể làm điều tương tự với các kiểu phức tạp hơn nhưng bạn cần tự hỏi bản thân mình có phải bạn vừa thực hiện truy vấn các thuộc tính của đối tượng đó quá khó đối với bản thân không? (vâng, trong trường hợp của tôi).

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

Tôi thấy giải pháp này khá tệ, nhưng nó thực sự là giải pháp lành mạnh duy nhất. Tất cả các tùy chọn đề nghị tham gia danh sách bằng cách sử dụng bất kỳ ký tự nào và sau đó tách nó trở lại có thể trở thành một mớ hỗn độn nếu ký tự tách được bao gồm trong các chuỗi. Json nên tỉnh táo hơn nhiều.
Mathieu VIALES

1
Cuối cùng, tôi đã đưa ra một câu trả lời là sự "hợp nhất" của câu trả lời này và câu trả lời khác để khắc phục từng vấn đề về câu trả lời (xấu / an toàn dữ liệu) bằng cách sử dụng điểm mạnh của câu trả lời kia.
Mathieu VIALES

13

Chỉ để đơn giản hóa -

Khung thực thể không hỗ trợ nguyên mẫu. Bạn có thể tạo một lớp để bọc nó hoặc thêm một thuộc tính khác để định dạng danh sách dưới dạng chuỗi:

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

1
Đây là trường hợp một mục danh sách không thể chứa một chuỗi. Nếu không, bạn sẽ cần phải thoát khỏi nó. Hoặc để tuần tự hóa / giải mã hóa danh sách cho các tình huống phức tạp hơn.
Adam Tal

3
Ngoài ra, đừng quên sử dụng [NotMapped] trên tài sản ICollection
Bến Petersen

7

Tất nhiên Pawel đã đưa ra câu trả lời đúng . Nhưng tôi thấy trong bài đăng này rằng vì EF 6+ có thể lưu các thuộc tính riêng tư. Vì vậy, tôi thích mã này hơn, vì bạn không thể lưu các Chuỗi theo cách sai.

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
Điều gì xảy ra nếu chuỗi chứa dấu phẩy?
Chalky

4
Tôi không khuyên bạn nên làm theo cách này. StringsAsStringssẽ chỉ được cập nhật khi Strings tham chiếu được thay đổi và thời gian duy nhất trong ví dụ của bạn xảy ra là lúc chuyển nhượng. Việc thêm hoặc xóa các mục khỏi Stringsdanh sách của bạn sau khi chỉ định sẽ không cập nhật StringsAsStringsbiến hỗ trợ. Cách thích hợp để thực hiện điều này là hiển thị StringsAsStringsdưới dạng một dạng xem Stringsdanh sách, thay vì theo cách khác. Nối các giá trị với nhau trong trình truy cập getcủa thuộc StringsAsStringstính và chia chúng trong trình truy cập set.
jduncanator

Để tránh thêm các thuộc tính riêng tư (không có tác dụng phụ), hãy đặt bộ cài của thuộc tính được tuần tự hóa thành riêng tư. jduncanator tất nhiên là đúng: nếu bạn không nắm bắt được các thao tác trong danh sách (sử dụng ObservableCollection?), các thay đổi sẽ không được EF nhận thấy.
Leonidas

Như @jduncanator đề cập giải pháp này không hoạt động khi một sửa đổi Danh mục được thực hiện (bắt buộc trong MVVM ví dụ)
Ihab Hajj

7

Điều chỉnh một chút câu trả lời của @Mathieu Viales , đây là đoạn mã tương thích .NET Standard sử dụng bộ tuần tự System.Text.Json mới, do đó loại bỏ sự phụ thuộc vào Newtonsoft.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));

Lưu ý rằng trong khi đối số thứ hai trong cả hai Serialize()Deserialize()thường là tùy chọn, bạn sẽ gặp lỗi:

Cây biểu thức không được chứa lệnh gọi hoặc lệnh gọi sử dụng các đối số tùy chọn

Đặt rõ ràng điều đó thành mặc định (null) cho mỗi lần xóa nó.


3

Bạn có thể sử dụng vùng ScalarCollectionchứa này để giới hạn một mảng và cung cấp một số tùy chọn thao tác ( Gist ):

Sử dụng:

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

Mã:

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
trông hơi quá thiết kế ?!
Falco Alexander

1
@FalcoAlexander Tôi đã cập nhật bài đăng của mình ... Có thể hơi dài dòng nhưng thực hiện công việc. Đảm bảo rằng bạn thay thế NET462bằng môi trường thích hợp hoặc thêm nó vào.
Shimmy Weitzhandler

1
+1 cho nỗ lực kết hợp điều này với nhau. Giải pháp là hơi quá mức cần thiết cho việc lưu trữ một mảng chuỗi :)
GETah
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.