EF Core 8 更新
.NET 8 现在内置支持在列中存储基本类型列表。在这里阅读有关原始集合 https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#primitive-collections.
EF Core 8 之前的版本(或者如果您想手动控制序列化而不是使用 JSON)
从 Entity Framework Core 开始,可以通过更简单的方式实现这一点2.1。 EF 现在支持价值转换 https://learn.microsoft.com/en-us/ef/core/modeling/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 的转换)。
另一个有趣的事实是空值是not传递到转换例程中,而是由框架本身处理。因此,无需担心转换例程内的空检查。然而,整个财产变成null
如果数据库包含NULL
value.
价值比较器怎么样?
使用此转换器创建迁移会导致以下警告:
属性“Entity.SomeListOfValues”是具有值转换器但没有值比较器的集合或枚举类型。设置值比较器以确保正确比较集合/枚举元素。
为建议的转换器设置正确的比较器取决于列表的语义。例如,如果你不关心其元素的顺序,您可以使用以下比较器:
new ValueComparer<IEnumerable<string>>(
(c1, c2) => new HashSet<string>(c1!).SetEquals(new HashSet<string>(c2!)),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()
);
使用此比较器,具有相同元素的重新排序列表不会被检测为更改,因此可以避免与数据库的往返。有关值比较器主题的更多信息,请考虑docs https://learn.microsoft.com/en-us/ef/core/modeling/value-comparers?tabs=ef5.
更新 EF Core 6.0
为了受益于Entity Framework Core 6.0 编译模型 https://devblogs.microsoft.com/dotnet/announcing-entity-framework-core-6-0-preview-5-compiled-models/,我们可以使用通用重载HasConversion
。所以完整的图就变成了:
builder.Entity<Foo>()
.Property(nameof(Foo.Bar))
.HasConversion<SemicolonSplitStringConverter, SplitStringComparer>();
...
public class SplitStringComparer : ValueComparer<IEnumerable<string>>
{
public SplitStringComparer() : base(
(c1, c2) => new HashSet<string>(c1!).SetEquals(new HashSet<string>(c2!)),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())))
{
}
}
public abstract class SplitStringConverter : ValueConverter<IEnumerable<string>, string>
{
protected SplitStringConverter(char delimiter) : base(
v => string.Join(delimiter.ToString(), v),
v => v.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries))
{
}
}
public class SemicolonSplitStringConverter : SplitStringConverter
{
public SemicolonSplitStringConverter() : base(';')
{
}
}