Quiero almacenar un objeto que contiene una Lista de primitivas usando EF.
public class MyObject {
public int Id {get;set;}
public virtual IList<int> Numbers {get;set;}
}
Sé que EF no puede almacenar esto, pero me gustaría conocer posibles soluciones para resolver este problema.
Las 2 soluciones que puedo pensar son:
1. Cree un objeto Dummy que tenga un ID y el valor integral, por ejemplo,
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. Almacene los valores de la lista como un blob , por ejemplo
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(",");
}
}
}
El problema con el enfoque 2. es que tengo que crear una implementación de IList personalizada para realizar un seguimiento si alguien modifica la colección devuelta.
¿Hay una mejor solución para esto ?
Aunque no me gusta responder a mi propia pregunta, pero aquí está lo que resolvió mi problema:
Después de encontrar este enlace sobre Tipos complejos , probé varias implementaciones y, después de algunos dolores de cabeza, terminé con esto.
Los valores de la Lista se almacenan como una cadena en la tabla directamente, por lo que no es necesario realizar varias combinaciones para obtener las entradas de la lista. Los implementadores solo tienen que implementar la conversación para cada entrada de lista en una cadena persistente (consulte el ejemplo de Código).
La mayor parte del código se maneja en Baseclass (PersistableScalarCollection). Solo tiene que derivar de él por tipo de datos (int, cadena, etc.) e implementar el método para serializar / deserializar el valor.
Es importante tener en cuenta que no puede usar la clase base genérica directamente (cuando elimina el resumen). Parece que EF no puede trabajar con eso. También debe asegurarse de anotar la clase derivada con el atributo [ComplexType]
.
También tenga en cuenta que no parece ser posible implementar un ComplexType para IList<T>
porque EF se queja sobre el indexador (por lo tanto, continué con ICollection).
También es importante tener en cuenta que, dado que todo está almacenado en una columna, no puede buscar valores en la Colección (al menos en la base de datos). En este caso, puede omitir esta implementación o desnormalizar los datos para la búsqueda.
Ejemplo para una colección de enteros:
/// <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();
}
}
Ejemplo de uso:
public class MyObject {
public int Id {get;set;}
public virtual PersistableIntCollection Numbers {get;set;}
}
Esta es la clase de base que maneja el aspecto de persistencia al almacenar las entradas de la lista dentro de una cadena:
/// <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
}