Je veux stocker un objet qui contient une liste de primitives utilisant EF.
public class MyObject {
public int Id {get;set;}
public virtual IList<int> Numbers {get;set;}
}
Je sais que EF ne peut pas stocker cela, mais j'aimerais connaître les solutions possibles pour résoudre ce problème.
Les 2 solutions auxquelles je peux penser sont:
1 .Créer un objet factice qui a un identifiant et la valeur intégrale, par ex.
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 .Enregistrer les valeurs de la liste sous forme de blob, par ex.
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(",");
}
}
}
Le problème avec l'approche 2. est que je dois créer une implémentation IList personnalisée pour suivre si quelqu'un modifie la collection retournée.
Y a-t-il une meilleure solution pour cela?
Bien que je n'aime pas répondre à ma propre question, mais voici ce qui a résolu mon problème:
Après avoir trouvé ce lien sur Types complexes J'ai essayé plusieurs implémentations, et après quelques maux de tête, je me suis retrouvé avec cela.
Les valeurs List sont stockées sous forme de chaîne directement sur la table, il n'est donc pas nécessaire d'effectuer plusieurs jointures pour obtenir les entrées de la liste. Les implémenteurs n'ont qu'à implémenter la conversation pour chaque entrée de liste dans une chaîne persistante (voir l'exemple de code).
La plupart du code est géré dans la classe de base (PersistableScalarCollection). Vous n'avez qu'à en dériver par type de données (int, chaîne, etc.) et implémenter la méthode pour sérialiser/désérialiser la valeur.
Il est important de noter que vous ne pouvez pas utiliser directement la classe de base générique (lorsque vous supprimez l'abstrait). Il semble qu'EF ne puisse pas travailler avec ça. Vous devez également vous assurer d'annoter la classe dérivée avec le [ComplexType]
attribut.
Notez également qu'il semble impossible d'implémenter un ComplexType pour IList<T>
parce que EF se plaint de l'indexeur (donc j'ai continué avec ICollection).
Il est également important de noter que, puisque tout est stocké dans une colonne, vous ne pouvez pas rechercher les valeurs dans la collection (au moins dans la base de données). Dans ce cas, vous pouvez ignorer cette implémentation ou dénormaliser les données pour la recherche.
Exemple pour une collection d'entiers:
/// <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();
}
}
Exemple d'utilisation:
public class MyObject {
public int Id {get;set;}
public virtual PersistableIntCollection Numbers {get;set;}
}
Il s'agit de la classe de base qui gère l'aspect persistance en stockant les entrées de liste dans une chaîne:
/// <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
}