用于映射字符串列表或 int 列表的实体框架选项 (List<string>)
Posted
技术标签:
【中文标题】用于映射字符串列表或 int 列表的实体框架选项 (List<string>)【英文标题】:Entity Framework options to map list of strings or list of int (List<string>) 【发布时间】:2012-08-12 16:34:30 【问题描述】:我想使用 EF 存储一个包含原语列表的对象。
public class MyObject
public int Id get;set;
public virtual IList<int> Numbers get;set;
我知道 EF 无法存储这个,但我想知道解决这个问题的可能解决方案。
我能想到的两个解决方案是:
1.创建一个具有 Id 和 Integervalue 的 Dummy 对象,例如
public class MyObject
public int Id get;set;
public virtual IList<MyInt> Numbers get;set;
public class MyInt
public int Id get;set;
public int Number get;set;
2.将列表值存储为 blob,例如
public class MyObject
public int Id get;set;
/// use NumbersValue to persist/load the list values
public string NumbersValue get;set;
[NotMapped]
public virtual IList<int> Numbers
get
return NumbersValue.split(',');
set
NumbersValue = value.ToArray().Join(",");
2. 方法的问题是,如果有人修改了返回的集合,我必须创建一个自定义 IList 实现来跟踪。
有没有更好的解决方案?
【问题讨论】:
"1.Create a Dummy object" 如果 EF 确实支持本机,这就是它在幕后所做的。我会选择这个。 我不喜欢这样,因为我只想存储一个基元列表,我不希望 EF 在另一个表上执行连接(这至少需要一个 Id 和值) - 这可能不是问题,但感觉不好。我为我的问题找到了一个可行的解决方案(见我的回答)。 - 我不会假装它是完美的。 但是您想为每个MyObject
存储一个单独的整数列表。这映射到每个MyObject.Id
的单独的整数列表——正是您想要避免的额外表。有多种原因为什么您不应该将它们全部塞入一个字符串中,但最清楚的两个(IMO)是它无法查询MyObject
s 包含的MyObject
s(例如) 10,如果连接所有数字会导致字符串太长而无法放入您的数据库列。
我不确定我是否理解正确。通过使用 ComplexType,List 数据(在本例中为字符串)存储在与 MyObject 相同的表中。我认为该解决方案对于我的方案是可以接受的,但是您是对的:我无法查询包含某些值的对象(至少在不使用字符串操作的情况下不能)-但在我的情况下,这不是必需的(我没有提到这一点)。可以存储的条目数是另一个很好的论点,但我认为最大值。 nvarchar(max) 上的字符数允许2^30-1 characters
是的,您对我的理解是正确的。如果您使用的是nvarchar(max)
,则该反对意见不适用。我在考虑 SQL Server 2000,它有一个 varchar(8000)
/nvarchar(4000)
最大值,除非你使用 text
/ntext
类型(行为不同)。 (它的存储效率仍然很低,但这不是问题。)FWIW,这被称为重复组,使您的数据库1NF(第一范式)意味着摆脱重复组。 (不要认为数据库规范化在定义上总是好的,也有例外。)
【参考方案1】:
虽然我不喜欢回答我自己的问题,但这里解决了我的问题:
在我找到这个关于Complex Types 的链接后,我尝试了几种实现方式,经过一番头疼后,我最终得到了这个。
列表值直接作为字符串存储在表中,因此无需执行多个连接即可获取列表条目。实现者只需将每个列表条目的对话实现为可持久字符串(参见代码示例)。
大部分代码在基类 (PersistableScalarCollection) 中处理。您只需从每个数据类型(int、string 等)派生并实现方法来序列化/反序列化值。
请务必注意,您不能直接使用泛型基类(当您删除抽象时)。似乎 EF 无法使用它。您还必须确保使用[ComplexType]
属性注释派生类。
另请注意,似乎不可能为 IList<T>
实现 ComplexType,因为 EF 抱怨索引器(因此我继续使用 ICollection)。
还需要注意的是,由于所有内容都存储在一列中,因此您无法搜索 Collection 中的值(至少在数据库中)。在这种情况下,您可以跳过此实现或对数据进行非规范化以进行搜索。
整数集合示例:
/// <summary>
/// ALlows persisting of a simple integer collection.
/// </summary>
[ComplexType]
public class PersistableIntCollection : PersistableScalarCollection<int>
protected override int ConvertSingleValueToRuntime(string rawValue)
return int.Parse(rawValue);
protected override string ConvertSingleValueToPersistable(int value)
return value.ToString();
使用示例:
public class MyObject
public int Id get;set;
public virtual PersistableIntCollection Numbers get;set;
这是通过将列表条目存储在字符串中来处理持久性方面的基类:
/// <summary>
/// Baseclass that allows persisting of scalar values as a collection (which is not supported by EF 4.3)
/// </summary>
/// <typeparam name="T">Type of the single collection entry that should be persisted.</typeparam>
[ComplexType]
public abstract class PersistableScalarCollection<T> : ICollection<T>
// use a character that will not occur in the collection.
// this can be overriden using the given abstract methods (e.g. for list of strings).
const string DefaultValueSeperator = "|";
readonly string[] DefaultValueSeperators = new string[] DefaultValueSeperator ;
/// <summary>
/// The internal data container for the list data.
/// </summary>
private List<T> Data get; set;
public PersistableScalarCollection()
Data = new List<T>();
/// <summary>
/// Implementors have to convert the given value raw value to the correct runtime-type.
/// </summary>
/// <param name="rawValue">the already seperated raw value from the database</param>
/// <returns></returns>
protected abstract T ConvertSingleValueToRuntime(string rawValue);
/// <summary>
/// Implementors should convert the given runtime value to a persistable form.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected abstract string ConvertSingleValueToPersistable(T value);
/// <summary>
/// Deriving classes can override the string that is used to seperate single values
/// </summary>
protected virtual string ValueSeperator
get
return DefaultValueSeperator;
/// <summary>
/// Deriving classes can override the string that is used to seperate single values
/// </summary>
protected virtual string[] ValueSeperators
get
return DefaultValueSeperators;
/// <summary>
/// DO NOT Modeify manually! This is only used to store/load the data.
/// </summary>
public string SerializedValue
get
var serializedValue = string.Join(ValueSeperator.ToString(),
Data.Select(x => ConvertSingleValueToPersistable(x))
.ToArray());
return serializedValue;
set
Data.Clear();
if (string.IsNullOrEmpty(value))
return;
Data = new List<T>(value.Split(ValueSeperators, StringSplitOptions.None)
.Select(x => ConvertSingleValueToRuntime(x)));
#region ICollection<T> Members
public void Add(T item)
Data.Add(item);
public void Clear()
Data.Clear();
public bool Contains(T item)
return Data.Contains(item);
public void CopyTo(T[] array, int arrayIndex)
Data.CopyTo(array, arrayIndex);
public int Count
get return Data.Count;
public bool IsReadOnly
get return false;
public bool Remove(T item)
return Data.Remove(item);
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
return Data.GetEnumerator();
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
return Data.GetEnumerator();
#endregion
【讨论】:
不错!我认为这比创建一个包含整数列的表更好 @Rumble ComplexType 属性位于System.ComponentModel.DataAnnotations.Schema
命名空间中,在EntityFramework
程序集中。我认为在从 Entity Framework 4.3 升级到 Entity Framework 5.0 之后,其中一些发生了变化
我很抱歉,但是为什么有一个包含 Id 和 N 值的表是错误的?毕竟,这是一对多的关系。使用一对多没有什么问题,因为您将始终使用 na 整数索引列访问数据。
@Tasio 抱歉迟到了...带有模式/索引的 XML 列将是一个有趣的想法。但是 EF 不支持直接查询 XML 列(没有 linq,例如过滤总是必须在内存中而不是在数据库中进行),但是您可以在手动 SQL、SP 中使用 XPAth 查询等...并让 EF 映射结果到您的对象(尽管您可以搜索 xml 数据,但根据我的经验,这总是很慢,并且仅适用于小型数据集)。所以这对我的例子来说是一个优势。正如我所提到的,我的示例仅适用于您不想搜索数据而只存储信息的情况。
@BernhardKircher - 嗨,我正在使用 EF Core,但收到此错误 The property 'Cameras.YourList' is of type 'PersistableStringCollection' which is not supported by current database provider. Either change the property CLR type or manually configure the database type for it.
【参考方案2】:
我正在使用 EF Core,遇到了类似的问题,但以更简单的方式解决了。
这个想法是将整数列表作为逗号分隔的字符串存储在数据库中。我通过在我的实体类型构建器中指定 ValueConverter
来做到这一点。
public class MyObjectBuilder : IEntityTypeConfiguration<MyObject>
public void Configure(EntityTypeBuilder<MyObject> builder)
var intArrayValueConverter = new ValueConverter<int[], string>(
i => string.Join(",", i),
s => string.IsNullOrWhiteSpace(s) ? new int[0] : s.Split(new[] ',' ).Select(v => int.Parse(v)).ToArray());
builder.Property(x => x.Numbers).HasConversion(intArrayValueConverter);
更多信息可以在这里找到:https://entityframeworkcore.com/knowledge-base/37370476/how-to-persist-a-list-of-strings-with-entity-framework-core-
【讨论】:
以上是关于用于映射字符串列表或 int 列表的实体框架选项 (List<string>)的主要内容,如果未能解决你的问题,请参考以下文章