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

问题描述 投票:0回答:1

在玩 EF Core 7 JSON 列时,我试图达到我保存的 JSON 看起来像这样的地步:

{
    "Courses": [
        "123": {
            "RemoteCourseId": 123,
            "Delivery": {
                "Method": "email",
                "ModeTarget": "[email protected]",
                "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"
                        }
                    ]
                }
            ]
        }
    ]
}

我面临的挑战是如何获取标识符,换一种方式显示:

{
    "Courses": [
        "123": {},
        "789": {}
    ]
}

我想达到可以指定“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; }
    }

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

我不知道如何为

LearnerCustomDataDto.Courses
字段配置OwnedNavigationBuilder。

我的最终目标是能够使用类似的东西获得特定课程

LearnerCustomDataCourseDto dto = dbContext.LearnerCustomData.Single(l => l.Data.Courses[123]);

在这里使用字典是正确的方法吗?我当然愿意接受有关使用字典或更改为其他内容的建议。

更新:我尝试使用 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;
    }
}

上下文的更新使它看起来像这样:

modelBuilder.Entity<LearnerCustomDataEntity>()
                .OwnsOne(l => l.Data, onb =>
                {
                    onb.OwnsOne(lcde => lcde.Profile);
                    onb.OwnsMany(lcde => lcde.Courses);
                    onb.ToJson();
                });

然而,当我试图删除一个实体时,我得到一个

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)

由以下因素触发:

_db.LearnerCustomData.SingleOrDefault(l => l.LtiUserId == defaultUser.Id);
c# entity-framework
1个回答
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);
}

只需为漂亮的属性名称添加一些属性即可完成。

但是无论如何你都应该定义

ViewModel
/
DTO
类,你仍然会在查询物化或getter属性中有
ToDictionary
但在代码审查期间不会受到打击。

你的最终目标看起来就像通过两个 ID 选择实体,所以只需做简单的选择语句。

© www.soinside.com 2019 - 2024. All rights reserved.