2016-05-22 7 views
9

Nehmen wir an, dass wir eine Klasse haben, die wie folgt aussieht:Wie behalte ich eine Liste von Strings mit Entity Framework Core?

public class Entity 
{ 
    public IList<string> SomeListOfValues { get; set; } 

    // Other code 
} 

Nun nehmen wir dies mit EF-Core-Code zunächst anhalten wollen und dass wir eine RDMBS wie SQL Server verwenden.

Ein möglicher Ansatz ist offensichtlich eine wraper Klasse Wraper zu schaffen, die die Zeichenfolge hüllt:

public class Wraper 
{ 
    public int Id { get; set; } 

    public string Value { get; set; } 
} 

Und die Klasse zu Refactoring, so dass es jetzt auf einer Liste von Wraper Objekten abhängt. In diesem Fall würde EF eine Tabelle für Entity, eine Tabelle für Wraper erzeugen und eine "Eins-zu-viele" Relation aufbauen: für jede Entität gibt es eine Reihe von Wrappern.

Obwohl dies funktioniert, mag ich den Ansatz nicht ganz, weil wir ein sehr einfaches Modell wegen Persistenzproblemen ändern. In der Tat, nur an das Domänenmodell und den Code denken, ohne die Persistenz, ist die Wraper Klasse dort ziemlich sinnlos.

Gibt es eine andere Möglichkeit, eine Entität mit einer Liste von Zeichenfolgen an einem RDBMS mit EF Core Code zu verankern? Zuerst anders als eine Wraper-Klasse zu erstellen? Natürlich muss am Ende dasselbe getan werden: Eine andere Tabelle muss erstellt werden, um die Strings zu halten, und eine "Eins-zu-Viele" -Beziehung muss vorhanden sein. Ich möchte dies nur mit EF Core tun, ohne die Wraper-Klasse im Domänenmodell codieren zu müssen.

+0

Dies ist einer der fehlt von EF aller Zeiten, die von NHibernate immer abgedeckt wurde: Benutzertypen ......... –

Antwort

3

Sie haben Recht, Sie möchten Ihr Domänenmodell nicht mit Persistenzproblemen bestreuen. Die Wahrheit ist, wenn Sie Ihr Modell für Ihre Domain und Persistenz verwenden, können Sie das Problem nicht vermeiden. Vor allem mit Entity Framework.

Die Lösung ist, bauen Sie Ihr Domänenmodell, ohne über die Datenbank überhaupt nachzudenken. Erstellen Sie dann eine separate Ebene, die für die Übersetzung verantwortlich ist. Etwas im Sinne des "Repository" -Musters.

Natürlich, jetzt haben Sie zweimal die Arbeit. Es liegt also an Ihnen, das richtige Gleichgewicht zwischen der Sauberkeit Ihres Modells und der zusätzlichen Arbeit zu finden. Tipp: Die zusätzliche Arbeit lohnt sich bei größeren Anwendungen.

-1

Ich habe einen Trick gefunden, und ich denke, dies ist eine sehr nützliche Abhilfe ist diese Art des Problems zu lösen:

public class User 
{ 
    public long UserId { get; set; } 

    public string Name { get; set; } 

    private string _stringArrayCore = string.Empty; 

    // Warnning: do not use this in Bussines Model 
    public string StringArrayCore 
    { 
    get 
    { 
     return _stringArrayCore; 
    } 

    set 
    { 
     _stringArrayCore = value; 
    } 
    } 

    [NotMapped] 
    public ICollection<string> StringArray 
    { 
    get 
    { 
     var splitString = _stringArrayCore.Split(';'); 
     var stringArray = new Collection<string>(); 

     foreach (var s in splitString) 
     { 
     stringArray.Add(s); 
     } 
     return stringArray; 
    } 
    set 
    { 
     _stringArrayCore = string.Join(";", value); 
    } 
    } 
} 

Wie zu verwenden:

// Write user 
    using (var userDbContext = new UserSystemDbContext()) 
    { 
    var user = new User { Name = "User", StringArray = new Collection<string>() { "Bassam1", "Bassam2" } }; 
    userDbContext.Users.Add(user); 
    userDbContext.SaveChanges(); 
    } 

    // Read User 
    using (var userDbContext = new UserSystemDbContext()) 
    { 
    var user = userDbContext.Users.ToList().Last(); 

    foreach (var userArray in user.StringArray) 
    { 
     Console.WriteLine(userArray); 
    } 
    } 

in der Datenbank

Tabelle Benutzer:

UserId | Name | StringArrayCore 
1  | User | Bassam1;Bassam2 
+0

nicht gut. Sehen Sie, was passiert, wenn Sie 'user.StringArray.Add (" Something ")'. –

+0

Das Muster sollte immer das ganze Array setzen und nicht die Elemente hinzufügen und entfernen! Wenn Sie das Feature (Add/Remove from collection) möchten, dann müssen Sie die Logik dafür im Setter und Getter oder custome der Sammlung erstellen. –

+0

Sicher. Was ich versuche zu sagen ist, dass es mehr als vernünftig ist zu erwarten, dass man Objekte mit einer ICollection-Eigenschaft hinzufügen und entfernen kann, also sollte man das unterstützen. –

3

Sie könnten die immer nützliche AutoMapper in Ihrem Repository verwenden, um dies zu erreichen, während die Dinge sauber zu halten.

Etwas wie:

MyEntity.cs

public class MyEntity 
{ 
    public int Id { get; set; } 
    public string SerializedListOfStrings { get; set; } 
} 

MyEntityDto.cs

public class MyEntityDto 
{ 
    public int Id { get; set; } 
    public IList<string> ListOfStrings { get; set; } 
} 

Stellen Sie die AutoMapper Mapping-Konfiguration in Ihrem Startup.cs up:

Mapper.Initialize(cfg => cfg.CreateMap<MyEntity, MyEntityDto>() 
    .ForMember(x => x.ListOfStrings, opt => opt.MapFrom(src => src.SerializedListOfStrings.Split(';')))); 
Mapper.Initialize(cfg => cfg.CreateMap<MyEntityDto, MyEntity>() 
    .ForMember(x => x.SerializedListOfStrings, opt => opt.MapFrom(src => string.Join(";", src.ListOfStrings)))); 

schließlich die Zuordnung in MyEntityRepository.cs verwenden, so dass Sie Ihre Zeit leider nicht Geschäftslogik haben darüber, wie das wissen oder Pflege Liste wird für die Persistenz behandelt:

public class MyEntityRepository 
{ 
    private readonly AppDbContext dbContext; 
    public MyEntityRepository(AppDbContext context) 
    { 
     dbContext = context; 
    } 

    public MyEntityDto Create() 
    { 
     var newEntity = new MyEntity(); 
     dbContext.MyEntities.Add(newEntity); 

     var newEntityDto = Mapper.Map<MyEntityDto>(newEntity); 

     return newEntityDto; 
    } 

    public MyEntityDto Find(int id) 
    { 
     var myEntity = dbContext.MyEntities.Find(id); 

     if (myEntity == null) 
      return null; 

     var myEntityDto = Mapper.Map<MyEntityDto>(myEntity); 

     return myEntityDto; 
    } 

    public MyEntityDto Save(MyEntityDto myEntityDto) 
    { 
     var myEntity = Mapper.Map<MyEntity>(myEntityDto); 

     dbContext.MyEntities.Save(myEntity); 

     return Mapper.Map<MyEntityDto>(myEntity); 
    } 
} 
0

ich eine mögliche Lösung implementiert, indem eine neue StringBackedList Klasse zu schaffen, in dem die aktuelle Liste Inhalt durch einen String unterstützt wird. Es funktioniert, indem die Backing-Zeichenkette jedes Mal aktualisiert wird, wenn die Liste geändert wird, wobei Newtonsoft.Json als Serializer verwendet wird (weil ich das bereits in meinem Projekt verwende, aber alle funktionieren).

Sie verwenden die Liste wie folgt aus:

public class Entity 
{ 
    // that's what stored in the DB, and shouldn't be accessed directly 
    public string SomeListOfValuesStr { get; set; } 

    [NotMapped] 
    public StringBackedList<string> SomeListOfValues 
    { 
     get 
     { 
      // this can't be created in the ctor, because the DB isn't read yet 
      if (_someListOfValues == null) 
      { 
       // the backing property is passed 'by reference' 
       _someListOfValues = new StringBackedList<string>(() => this.SomeListOfValuesStr); 
      } 
      return _someListOfValues; 
     } 
    } 
    private StringBackedList<string> _someListOfValues; 
} 

Hier ist die Umsetzung der StringBackedList Klasse. Um die Verwendung zu erleichtern, wird die Eigenschaft backing unter Verwendung von this solution als Referenz übergeben.

using Newtonsoft.Json; 
using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq.Expressions; 
using System.Reflection; 

namespace Model 
{ 
    public class StringBackedList<T> : IList<T> 
    { 
     private readonly Accessor<string> _backingStringAccessor; 
     private readonly IList<T> _backingList; 

     public StringBackedList(Expression<Func<string>> expr) 
     { 
      _backingStringAccessor = new Accessor<string>(expr); 

      var initialValue = _backingStringAccessor.Get(); 
      if (initialValue == null) 
       _backingList = new List<T>(); 
      else 
       _backingList = JsonConvert.DeserializeObject<IList<T>>(initialValue); 
     } 

     public T this[int index] { 
      get => _backingList[index]; 
      set { _backingList[index] = value; Store(); } 
     } 

     public int Count => _backingList.Count; 

     public bool IsReadOnly => _backingList.IsReadOnly; 

     public void Add(T item) 
     { 
      _backingList.Add(item); 
      Store(); 
     } 

     public void Clear() 
     { 
      _backingList.Clear(); 
      Store(); 
     } 

     public bool Contains(T item) 
     { 
      return _backingList.Contains(item); 
     } 

     public void CopyTo(T[] array, int arrayIndex) 
     { 
      _backingList.CopyTo(array, arrayIndex); 
     } 

     public IEnumerator<T> GetEnumerator() 
     { 
      return _backingList.GetEnumerator(); 
     } 

     public int IndexOf(T item) 
     { 
      return _backingList.IndexOf(item); 
     } 

     public void Insert(int index, T item) 
     { 
      _backingList.Insert(index, item); 
      Store(); 
     } 

     public bool Remove(T item) 
     { 
      var res = _backingList.Remove(item); 
      if (res) 
       Store(); 
      return res; 
     } 

     public void RemoveAt(int index) 
     { 
      _backingList.RemoveAt(index); 
      Store(); 
     } 

     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return _backingList.GetEnumerator(); 
     } 

     public void Store() 
     { 
      _backingStringAccessor.Set(JsonConvert.SerializeObject(_backingList)); 
     } 
    } 

    // this class comes from https://stackoverflow.com/a/43498938/2698119 
    public class Accessor<T> 
    { 
     private Action<T> Setter; 
     private Func<T> Getter; 

     public Accessor(Expression<Func<T>> expr) 
     { 
      var memberExpression = (MemberExpression)expr.Body; 
      var instanceExpression = memberExpression.Expression; 
      var parameter = Expression.Parameter(typeof(T)); 
      if (memberExpression.Member is PropertyInfo propertyInfo) 
      { 
       Setter = Expression.Lambda<Action<T>>(Expression.Call(instanceExpression, propertyInfo.GetSetMethod(), parameter), parameter).Compile(); 
       Getter = Expression.Lambda<Func<T>>(Expression.Call(instanceExpression, propertyInfo.GetGetMethod())).Compile(); 
      } 
      else if (memberExpression.Member is FieldInfo fieldInfo) 
      { 
       Setter = Expression.Lambda<Action<T>>(Expression.Assign(memberExpression, parameter), parameter).Compile(); 
       Getter = Expression.Lambda<Func<T>>(Expression.Field(instanceExpression, fieldInfo)).Compile(); 
      } 

     } 

     public void Set(T value) => Setter(value); 
     public T Get() => Getter(); 
    } 
} 

Caveats: die Träger Zeichenfolge nur aktualisiert wird, wenn die Liste selbst verändert wird. Das Aktualisieren eines Listenelements über direkten Zugriff (z. B. über den Listenindexer) erfordert einen manuellen Aufruf der Methode Store().

0

Dies könnte spät sein, aber Sie können nie sagen, wer es helfen könnte. Siehe meine Lösung auf der Grundlage der vorherigen Antwort

Erstens, werden Sie diese Referenz für eine Standard-Liste

public class ListObservableCollection<T> : ObservableCollection<T> 
{ 
    public ListObservableCollection() : base() 
    { 

    } 


    public ListObservableCollection(IEnumerable<T> collection) : base(collection) 
    { 

    } 


    public ListObservableCollection(List<T> list) : base(list) 
    { 

    } 
    public static implicit operator ListObservableCollection<T>(List<T> val) 
    { 
     return new ListObservableCollection<T>(val); 
    } 
} 

Dann using System.Collections.ObjectModel;

dann die ObservableCollection erweitern und fügen Sie eine implizite Betreiber Überlastung müssen erstellen eine abstrakte EnityString Klasse (Dies ist, wo die guten Sachen passiert)

public abstract class EntityString 
{ 
    [NotMapped] 
    Dictionary<string, ListObservableCollection<string>> loc = new Dictionary<string, ListObservableCollection<string>>(); 
    protected ListObservableCollection<string> Getter(ref string backingFeild, [CallerMemberName] string propertyName = null) 
    { 


     var file = backingFeild; 
     if ((!loc.ContainsKey(propertyName)) && (!string.IsNullOrEmpty(file))) 
     { 
      loc[propertyName] = GetValue(file); 
      loc[propertyName].CollectionChanged += (a, e) => SetValue(file, loc[propertyName]); 
     } 
     return loc[propertyName]; 
    } 

    protected void Setter(ref string backingFeild, ref ListObservableCollection<string> value, [CallerMemberName] string propertyName = null) 
    { 

     var file = backingFeild; 
     loc[propertyName] = value; 
     SetValue(file, value); 
     loc[propertyName].CollectionChanged += (a, e) => SetValue(file, loc[propertyName]); 
    } 

    private List<string> GetValue(string data) 
    { 
     if (string.IsNullOrEmpty(data)) return new List<string>(); 
     return data.Split(';').ToList(); 
    } 

    private string SetValue(string backingStore, ICollection<string> value) 
    { 

     return string.Join(";", value); 
    } 

} 

T enn es verwenden wie so

public class Categorey : EntityString 
{ 

    public string Id { get; set; } 
    public string Name { get; set; } 


    private string descriptions = string.Empty; 

    public ListObservableCollection<string> AllowedDescriptions 
    { 
     get 
     { 
      return Getter(ref descriptions); 
     } 
     set 
     { 
      Setter(ref descriptions, ref value); 
     } 
    } 


    public DateTime Date { get; set; } 
}