我想使用EF存储包含基元列表的对象。
public class MyObject {
public int Id {get;set;}
public virtual IList<int> Numbers {get;set;}
}
我知道 EF无法存储,但我想知道解决此问题的可能解决方案。
我能想到的两个解决方案是:
1. 创建一个具有Id和整数值的虚拟对象,例如
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实现来跟踪是否有人修改了返回的集合。
这是否有更好的解决方案?
答案 0 :(得分:37)
虽然我不想回答我自己的问题,但是这里解决了我的问题:
在我找到关于Complex Types的链接后,我尝试了几个实现,经过一些头痛我最终得到了这个。
List值直接作为字符串存储在表中,因此不需要执行多个连接以获取列表条目。实现者只需要将每个列表条目的对话实现为可持久化的字符串(参见代码示例)。
大多数代码都在Baseclass(PersistableScalarCollection)中处理。您只需从每个数据类型(int,string等)派生它,并实现该方法以序列化/反序列化该值。
重要的是要注意,您不能直接使用通用基类(当您删除摘要时)。似乎EF无法解决这个问题。您还必须确保使用[ComplexType]
属性注释派生类。
另请注意,似乎无法为IList<T>
实现ComplexType,因为EF会抱怨Indexer(因此我继续使用ICollection)。
同样重要的是要注意,由于所有内容都存储在一列中,因此无法搜索以查找集合中的值(至少在数据库中)。在这种情况下,您可以跳过此实现或对数据进行非规范化以进行搜索。
整数集合示例:
/// <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
}
答案 1 :(得分: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);
}
}