Ich möchte ein Objekt speichern, das eine Liste von Grundelementen mit EF enthält.
public class MyObject {
public int Id {get;set;}
public virtual IList<int> Numbers {get;set;}
}
Ich weiß, dass EF dies nicht speichern kann, aber ich würde gerne nach möglichen Lösungen zur Lösung dieses Problems suchen.
Die 2 Lösungen, die mir einfallen, sind:
1. Erstellen Sie ein Dummy-Objekt mit einer ID und dem Integervalue, z
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. Speichern Sie die Listenwerte als Blob , z
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(",");
}
}
}
Das Problem mit dem 2. Ansatz ist, dass ich eine benutzerdefinierte IList-Implementierung erstellen muss, um zu verfolgen, ob jemand die zurückgegebene Sammlung ändert.
Gibt es dafür eine bessere Lösung ?
Ich beantworte zwar nicht gerne meine eigene Frage, aber hier ist, was mein Problem gelöst hat:
Nachdem ich diesen Link über komplexe Typen gefunden hatte, versuchte ich mehrere Implementierungen, und nach einigen Kopfschmerzen endete ich damit.
Die Listenwerte werden direkt als Zeichenfolge in der Tabelle gespeichert. Es müssen also nicht mehrere Joins ausgeführt werden, um die Listeneinträge abzurufen. Implementierer müssen die Konversation nur für jeden Listeneintrag in eine dauerhafte Zeichenfolge implementieren (siehe Codebeispiel).
Der größte Teil des Codes wird in der Baseclass (PersistableScalarCollection) behandelt. Sie müssen nur nach Datentyp (int, string usw.) davon abgeleitet werden und die Methode implementieren, um den Wert zu serialisieren / deserialisieren.
Es ist wichtig zu wissen , dass Sie die generische Baseclass nicht direkt verwenden können (wenn Sie die Zusammenfassung entfernen). Es scheint, dass EF damit nicht arbeiten kann. Sie müssen außerdem sicherstellen, dass die abgeleitete Klasse mit dem Attribut [ComplexType]
wird.
Beachten Sie auch, dass es nicht möglich ist, einen ComplexType für IList<T>
zu implementieren, da EF sich über den Indexer beschwert (daher habe ich mit ICollection fortgefahren).
Beachten Sie außerdem, dass Sie, da alles in einer Spalte gespeichert ist, nicht in der Collection nach Werten suchen können (zumindest in der Datenbank). In diesem Fall können Sie diese Implementierung überspringen oder die Daten für die Suche denormalisieren.
Beispiel für eine Ganzzahlsammlung:
/// <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();
}
}
Anwendungsbeispiel:
public class MyObject {
public int Id {get;set;}
public virtual PersistableIntCollection Numbers {get;set;}
}
Dies ist die Baseclass, die den Persistenzaspekt durch Speichern der Listeneinträge in einer Zeichenfolge verarbeitet:
/// <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
}