Ответ 1
Хотя я не люблю отвечать на свой вопрос, но вот что решило мою проблему:
После того, как я нашел эту ссылку о Сложные типы, я попробовал несколько реализаций, и после некоторой головной боли я закончил с это.
Значения List сохраняются в виде строки в таблице напрямую, поэтому для получения записей списка не требуется выполнять несколько объединений. Разработчики должны выполнить цепочку для каждой записи списка в неизменяемой строке (см. Пример кода).
Большая часть кода обрабатывается в Baseclass (PersistableScalarCollection). Вам нужно извлечь из него только один тип данных (int, string и т.д.) И реализовать метод сериализации/десериализации значения.
Важно отметить, что вы не можете напрямую использовать базовый базовый класс (при удалении абстрактного текста). Кажется, что EF не может с этим справиться. Вы также должны обязательно аннотировать производный класс атрибутом [ComplexType]
.
Также обратите внимание, что кажется, что не возможно реализовать ComplexType для IList<T>
, потому что 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
}