Voglio memorizzare un oggetto che contiene un elenco di primitivi usando EF.
public class MyObject {
public int Id {get;set;}
public virtual IList<int> Numbers {get;set;}
}
So che EF non può memorizzarlo , ma mi piacerebbe conoscere le possibili soluzioni per risolvere questo problema.
Le 2 soluzioni a cui posso pensare sono:
1. Creare un oggetto fittizio con ID e Integervalue, ad es
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. Memorizzare i valori della lista come un blob , ad es
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(",");
}
}
}
Il problema con l'approccio 2. è che devo creare un'implementazione personalizzata di IList per tenere traccia se qualcuno modifica la collezione restituita.
C'è una soluzione migliore per questo ?
Anche se non mi piace rispondere alla mia domanda, ma ecco cosa ha risolto il mio problema:
Dopo aver trovato questo collegamento sui Tipi complessi ho provato diverse implementazioni e, dopo un po 'di mal di testa, ho finito con questo.
I valori dell'elenco vengono memorizzati direttamente come una stringa direttamente nella tabella, quindi non è necessario eseguire diversi join per ottenere le voci dell'elenco. Gli implementatori devono solo implementare la conversazione per ciascuna voce di elenco in una stringa persistibile (vedere l'esempio di codice).
La maggior parte del codice viene gestita nel Baseclass (PersistableScalarCollection). Devi solo derivare da esso per tipo di dati (int, string, ecc.) E implementare il metodo per serializzare / deserializzare il valore.
È importante notare che non è possibile utilizzare direttamente la baseclass generica (quando si rimuove l'abstract). Sembra che EF non possa lavorare con quello. È inoltre necessario assicurarsi di annotare la classe derivata con l'attributo [ComplexType]
.
Si noti inoltre che non sembra possibile implementare un ComplexType per IList<T>
perché EF si lamenta dell'indicizzatore (quindi ho proseguito con ICollection).
È anche importante notare che, poiché tutto è archiviato all'interno di una colonna, non è possibile cercare valori nella Raccolta (almeno sul database). In questo caso è possibile saltare questa implementazione o denormalizzare i dati per la ricerca.
Esempio per una raccolta di numeri interi:
/// <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();
}
}
Esempio di utilizzo:
public class MyObject {
public int Id {get;set;}
public virtual PersistableIntCollection Numbers {get;set;}
}
Questa è la classe di base che gestisce l'aspetto di persistenza memorizzando le voci della lista all'interno di una stringa:
/// <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
}