Chr*_*ell 6 c# entity-framework
在使用 EF Core 7 JSON Column 时,我试图达到我保存的 JSON 如下所示的程度:
{
"Courses": [
"123": {
"RemoteCourseId": 123,
"Delivery": {
"Method": "email",
"ModeTarget": "email@example.org",
"Time": "13:30:00"
},
"Quizzes": [
"54321": {
"RemoteQuizId": 54321,
"Credits": null,
"Details": {
"CompletedDate": null,
"StartDate": "2023-04-17T17:17:10.315684Z"
},
"Questions": [
"12345": {
"ExpirationDate": "2023-04-17T17:17:10.316138Z",
"IsCorrect": false,
"QuestionId": 12345,
"QuestionUrl": "https://stackoverflow.com",
"SentDate": "2023-04-17T17:17:10.316181Z"
}
]
}
]
}
]
}
Run Code Online (Sandbox Code Playgroud)
我面临的挑战是如何获取标识符,以另一种方式显示:
{
"Courses": [
"123": {},
"789": {}
]
}
Run Code Online (Sandbox Code Playgroud)
我想达到可以指定“123”和“789”的程度。我对此的尝试是使用Dictionary类似的方法:
public class LearnerCustomDataEntity
{
public int Id { get; set; }
public int LtiUserId { get; set; }
public LearnerCustomDataDto Data { get; set; }
}
public class LearnerCustomDataDto
{
public LearnerCustomDataProfileDto Profile { get; set; }
public Dictionary<int, LearnerCustomDataCourseDto> Courses { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
在 DbContext 中,我指定 JSON 列 ( LearnerCustomDataEntity.Data),如下所示:
modelBuilder.Entity<LearnerCustomDataEntity>()
.OwnsOne(l => l.Data, onb =>
{
onb.OwnsOne<LearnerCustomDataProfileDto>(lcde => lcde.Profile);
onb.OwnsOne(lcdc => lcdc.Courses, onb =>
{
// I'm not sure _how_ to configure this
onb.HasOne(l => l);
});
onb.ToJson();
});
Run Code Online (Sandbox Code Playgroud)
我不知道如何为该LearnerCustomDataDto.Courses字段配置 OwnedNavigationBuilder。
我的最终目标是能够使用类似的东西来获得特定的课程
LearnerCustomDataCourseDto dto = dbContext.LearnerCustomData.Single(l => l.Data.Courses[123]);
Run Code Online (Sandbox Code Playgroud)
使用字典是正确的方法吗?我当然愿意接受关于使用字典或更改为其他内容的建议。
更新:我尝试使用 KeyedCollection 实现,因为它可以实现通过密钥访问的目标,但我在转换方面遇到问题。因此,例如:
public class LearnerCustomDataDto
{
public LearnerCustomDataProfileDto Profile { get; set; }
public LearnerCustomDataCourseDtoCollection Courses { get; set; }
}
public class LearnerCustomDataCourseDtoCollection : KeyedCollection<int, LearnerCustomDataCourseDto>
{
protected override int GetKeyForItem(LearnerCustomDataCourseDto item)
{
return item.RemoteCourseId;
}
}
Run Code Online (Sandbox Code Playgroud)
上下文的更新使其看起来像这样:
modelBuilder.Entity<LearnerCustomDataEntity>()
.OwnsOne(l => l.Data, onb =>
{
onb.OwnsOne(lcde => lcde.Profile);
onb.OwnsMany(lcde => lcde.Courses);
onb.ToJson();
});
Run Code Online (Sandbox Code Playgroud)
但是,当尝试删除实体时,我得到InvalidOperationException:
System.InvalidOperationException : The requested operation requires an element of type 'Array', but the target element has type 'Object'.
Stack Trace:
[xUnit.net 00:00:46.97] at System.Text.Json.ThrowHelper.ThrowJsonElementWrongTypeException(JsonTokenType expectedType, JsonTokenType actualType)
[xUnit.net 00:00:46.98] at System.Text.Json.JsonElement.EnumerateArray()
[xUnit.net 00:00:46.98] at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.IncludeJsonEntityCollection[TIncludingEntity,TIncludedCollectionElement](QueryContext queryContext, Nullable`1 jsonElement, Object[] keyPropertyValues, TIncludingEntity entity, Func`4 innerShaper, Action`2 fixup)
[xUnit.net 00:00:46.98] at lambda_method654(Closure, QueryContext, Object[], JsonElement)
[xUnit.net 00:00:46.98] at lambda_method653(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
[xUnit.net 00:00:46.98] at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
[xUnit.net 00:00:46.98] at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found)
[xUnit.net 00:00:46.98] at lambda_method670(Closure, QueryContext)
[xUnit.net 00:00:46.98] at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
[xUnit.net 00:00:46.98] at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
Run Code Online (Sandbox Code Playgroud)
这是由以下因素触发的:
_db.LearnerCustomData.SingleOrDefault(l => l.LtiUserId == defaultUser.Id);
Run Code Online (Sandbox Code Playgroud)
最简单的方法是将 EF 导航属性与 JSON 字典分开。这是一个简单的技巧,应该适用于每个 ORM 和每个序列化器。
class ParentEntity
{
[JsonIgnore]
[XmlIgnore] [CsvIgnore] [FaxToCIAIgnore] //future proofing!
public HashSet<ChildEntity> Children { get; set; }
[NotMapped] //don't include this field in EF black magic
public Dictionary<int, ChildEntity> JsonChildren => Children.ToDictionary(c=>c.Id, c=>c);
}
Run Code Online (Sandbox Code Playgroud)
只需添加一些漂亮的属性名称的属性即可完成。
但无论如何你都应该定义ViewModel/DTO类,你仍然可以ToDictionary在查询具体化或 getter 属性中定义 / 类,但在代码审查期间不会受到批评。
您的最终目标看起来就像通过两个 ID 选择实体,因此只需制作简单的 select 语句即可。