2016-05-22 24 views
9

supponiamo che abbiamo una classe che è simile alla seguente:Come mantenere un elenco di stringhe con Entity Framework Core?

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

    // Other code 
} 

Ora, supponiamo di voler persistere questo utilizzando EF Nucleo codice prima e che stiamo utilizzando un RDMBS come SQL Server.

Un possibile approccio è ovviamente quello di creare una classe wraper Wraper che avvolge la stringa:

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

    public string Value { get; set; } 
} 

E di refactoring la classe in modo che ora dipende da un elenco di oggetti Wraper. In quel caso EF genererebbe una tabella per Entity, una tabella per Wraper e stabilirà una relazione "uno a molti": per ogni entità c'è un gruppo di wrapers.

Anche se questo funziona, non mi piace l'approccio perché stiamo cambiando un modello molto semplice a causa di preoccupazioni di persistenza. In effetti, pensando solo al modello di dominio e al codice, senza la persistenza, la classe Wraper non ha molto senso.

C'è un altro modo per mantenere un'entità con un elenco di stringhe su un RDBMS utilizzando il codice EF Core Prima di creare una classe wrapper? Naturalmente, alla fine, bisogna fare la stessa cosa: bisogna creare un altro tavolo per tenere le corde e una relazione "da uno a molti" deve essere in atto. Voglio solo farlo con EF Core senza dover codificare la classe wraper nel modello di dominio.

+0

Questo è uno dei più manca di EF di tutti i tempi, che è stato sempre coperto da NHibernate: tipi di utente ......... –

risposta

3

Hai ragione, non vuoi sporcare il tuo modello di dominio con problemi di persistenza. La verità è che se si utilizza lo stesso modello per il dominio e la persistenza, non sarà possibile evitare il problema. Soprattutto usando Entity Framework.

La soluzione è, crea il tuo modello di dominio senza pensare al database. Quindi crea un livello separato che è responsabile della traduzione. Qualcosa sulla falsariga del modello 'Deposito'.

Naturalmente, ora avete il doppio del lavoro. Quindi sta a te trovare il giusto equilibrio tra mantenere il tuo modello pulito e fare il lavoro extra. Suggerimento: il lavoro extra ne vale la pena in applicazioni più grandi.

-1

ho trovato un trucco e penso che questa sia una soluzione molto utile per risolvere questo tipo di problema:

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); 
    } 
    } 
} 

Modo d'uso:

// 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); 
    } 
    } 

nel database

Tabella Utenti:

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

Non va bene. Guarda cosa succede se esegui 'user.StringArray.Add (" Something ")'. –

+0

Il pattern u dovrebbe sempre impostare l'intero array e non aggiungere e rimuovere gli elementi! Se si desidera questa funzione (Aggiungi/Rimuovi dalla raccolta), è necessario creare la logica per tale impostazione nel setter e getter o custome della raccolta. –

+0

Sicuro. Quello che sto cercando di dire è che è più che ragionevole aspettarsi che si possano aggiungere e rimuovere elementi con una proprietà 'ICollection', quindi dovresti supportarlo. –

3

È possibile utilizzare il sempre utile AutoMapper nel proprio repository per ottenere ciò mantenendo le cose in ordine.

Qualcosa di simile:

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; } 
} 

Impostare la configurazione della mappatura automapper nelle Startup.cs:

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)))); 

Quindi, utilizzare la mappatura in MyEntityRepository.cs in modo che la logica di business doesnt devono sapere o cura di come la lista è gestita per la persistenza:

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

ho implementato una possibile soluzione per la creazione di una nuova classe di StringBackedList, in cui il contenuto effettivo lista è sostenuta da una stringa. Funziona aggiornando la stringa di supporto ogni volta che la lista viene modificata, usando Newtonsoft.Json come serializzatore (perché lo uso già nel mio progetto, ma qualsiasi funzionerebbe).

si utilizza l'elenco come questo:

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; 
} 

Ecco l'implementazione della classe StringBackedList. Per facilità d'uso, la proprietà di supporto viene passata per riferimento usando this solution.

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: la stringa di supporto viene aggiornata solo quando la lista stessa viene modificato. L'aggiornamento di un elemento di elenco tramite accesso diretto (ad esempio tramite l'indicizzatore di elenchi) richiede una chiamata manuale al metodo Store().

0

Potrebbe essere tardi, ma non si può mai dire a chi potrebbe essere d'aiuto. Vedere la mia soluzione basata sulla risposta precedente

In primo luogo, si sta andando ad avere bisogno di questo riferimento using System.Collections.ObjectModel;

quindi estendere l'ObservableCollection e aggiungere un sovraccarico operatore implicito per un elenco standard

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); 
    } 
} 

Quindi creare una classe astratta EnityString (Qui è dove accade il bello)

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 gallina usano in questo modo

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; } 
}