让我们假设我们有一个类如下:
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做这个,而不需要在域模型中编写包装类。
答案 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