Entity Framework Core 7 JSON 列 - 使用字典<int, Object>

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)

PTw*_*Twr 0

最简单的方法是将 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 语句即可。

  • 我不确定我是否遵循这一点 - 我不想忽略这些字段,我想利用 EF7 中新的 Json 列功能 (2认同)