如何使用Entity Framework Core保留字符串列表?

时间:2016-05-22 04:14:24

标签: c# entity-framework entity-framework-core .net-core

让我们假设我们有一个类如下:

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

    // Other code
}

现在,假设我们希望使用EF Core Code First来保持这一点,并且我们正在使用像SQL Server这样的RDMBS。

一种可能的方法显然是创建一个包装字符串Wraper,它包装字符串:

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

    public string Value { get; set; }
}

重构类,以便它现在依赖于Wraper个对象的列表。在这种情况下,EF将为Entity生成一个表格,Wraper生成一个表格,并确定一对多的表格#34;关系:每个实体都有一堆包装纸。

虽然这有效,但我不太喜欢这种方法,因为我们正在改变一个非常简单的模型,因为持久性问题。实际上,只考虑领域模型和代码,没有持久性,Wraper类在那里毫无意义。

除了创建一个包装类之外,还有其他任何方法使用EF Core Code First将带有字符串列表的实体持久保存到RDBMS吗?当然,最后必须做同样的事情:必须创建另一个表来保存字符串和一对多的#34;关系必须到位。我只想用EF Core做这个,而不需要在域模型中编写包装类。

6 个答案:

答案 0 :(得分:10)

从Entity Framework Core 2.1 开始,可以通过更简单的方式来实现。 EF现在支持Value Conversions来专门解决这种情况,在这种情况下,属性需要映射到其他类型进行存储。

要保留字符串集合,可以通过以下方式设置DbContext

protected override void OnModelCreating(ModelBuilder builder)
{
    var splitStringConverter = new ValueConverter<IEnumerable<string>, string>(v => string.Join(";", v), v => v.Split(new[] { ';' }));
    builder.Entity<Entity>().Property(nameof(Entity.SomeListOfValues)).HasConversion(splitStringConverter);
} 

请注意,此解决方案不会使您的业务类遇到数据库问题。

不用说这个解决方案,就必须确保字符串不能包含定界符。但是,当然,可以使用任何自定义逻辑进行转换(例如,从JSON到JSON的转换)。

另一个有趣的事实是,空值不是 传递给转换例程,而是由框架本身处理。因此,无需担心空检查。

答案 1 :(得分:4)

您可以在存储库中使用有用的AutoMapper来实现此目的,同时保持整洁。

类似的东西:

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

在Startup.cs中设置AutoMapper映射配置:

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

最后,使用MyEntityRepository.cs中的映射,以便您的业务逻辑不必知道或关心如何处理List以保持持久性:

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

答案 2 :(得分:3)

你是对的,你不想在持久性问题上乱丢你的域模型。事实是,如果你为你的域和持久性使用相同的模型,你将无法避免这个问题。特别是使用实体框架。

解决方案是,在不考虑数据库的情况下构建域模型。然后构建一个负责翻译的单独层。 “存储库”模式的一些东西。

当然,现在你有两倍的工作。因此,您需要在保持模型清洁和进行额外工作之间找到适当的平衡点。提示:额外的工作在更大的应用中是值得的。

答案 3 :(得分:1)

这可能会迟到,但你永远不知道它可能会有什么帮助。 根据之前的答案查看我的解决方案

首先,您需要此参考ObservableCollection<T>

然后扩展 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); } } 并为标准列表添加隐式运算符重载

EntityString

然后创建一个抽象的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); } } 类(这是好事发生的地方)

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

然后像这样使用

$string = "(ILLUSTRATION 1D) - AWS 10-Piece Inspection Toolkit";
$string = preg_replace("/(\(ILLUSTRATION.*\))(.*-|\s)(.*)/", "$3", $string);

答案 4 :(得分:0)

我通过创建一个新的StringBackedList类来实现一个可能的解决方案,其中实际的列表内容由字符串支持。它的工作原理是每当修改列表时更新后备字符串,使用Newtonsoft.Json作为序列化程序(因为我已经在我的项目中使用它,但任何都可以工作)。

您可以使用以下列表:

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

这是StringBackedList课程的实施。为了便于使用,使用this solution通过引用传递backing属性。

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

警告:只有在修改列表本身时才会更新支持字符串。通过直接访问(例如,通过列表索引器)更新列表元素需要手动调用Store()方法。

答案 5 :(得分:-1)

我找到了一个技巧,我认为这是解决此类问题的非常有用的解决方法:

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

使用方法:

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

在数据库中

表用户:

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