簡體   English   中英

ASP.NET Core 5 Blazor WASM、gRPC、Entity Framework Core 5:多對多導致堆棧溢出

[英]ASP.NET Core 5 Blazor WASM, gRPC, Entity Framework Core 5: many-to-many results in stack overflow

信息:

  • 使用:Visual Studio v16.9.0 Preview 2.0 和 .NET SDK 5.0.200
  • Kestrel 使用 SQLite DB 托管 Blazor WASM / gRPC / EF Core 項目。

示例項目:(完整的工作源代碼可在GitHub 獲得它是博客 cms 的(簡單)原型,在帖子和標簽之間具有多對多關系。

當我嘗試從BlogService.cs返回帶有標簽的帖子列表時,應用程序因“堆棧溢出”錯誤而停止。 在我看來,它就像一個參考循環,就像您使用 json 時得到的一樣。 但也許我錯了,我不知道。 (使用 Json.Net 您需要設置ReferenceLoopHandling = ReferenceLoopHandling.Ignore才能使多對多工作)

或者我可以對 LINQ 查詢進行更改,以便 BlogService.cs 中的.Include(tipd => tipd.TagsInPostData) BlogService.cs返回每個帖子中的標簽(一層深)並且不嘗試解析每個帖子中的所有帖子標簽等。

我還在學習 C#、EF Core、gRPC、LINQ,英語不是我的第一語言,所以我希望你能理解我的問題。 如果沒有,請說出來,我會努力做得更好。

(部分) BlogService.cs ,完整來源在這里

public override async Task<Posts> GetPosts(Empty request, ServerCallContext context)
{
    var posts = new Posts();
    var allPosts = await dbContext.Posts
        .Where(ps => ps.PostStat == PostStatus.Published)
        .Include(pa => pa.PostAuthor)
        .Include(pe => pe.PostExt)
        //.Include(tipd => tipd.TagsInPostData) // TODO: [ERROR] / doesn't work: results in a stack overflow.
        .OrderByDescending(dc => dc.DateCreated)
        .ToListAsync();
    posts.PostsData.AddRange(allPosts);
    return posts;
}

EF Core 從中創建表的類(包括連接表,EF Core “自動”創建該表)是從 protobuf 文件blog.proto創建的。 為了使用 gRPC 向連接表添加記錄,我在 GitHub 提出了一個問題,並在ApplicationDbContext.cs中相應地修改了我的代碼

(部分) ApplicationDbContext.cs ,完整源代碼在這里

modelBuilder
    .Entity<Post>()
    .HasMany(e => e.TagsInPostData)
    .WithMany(e => e.PostsInTagData)
    .UsingEntity<Dictionary<string, object>>(
        "PostsTags", // (Join) Table Name.(Renames EF Core autogenerated 'PostTag' table to 'PostsTags' table)
        b => b.HasOne<Tag>().WithMany().HasForeignKey("TagId"), // Field Name.
        b => b.HasOne<Post>().WithMany().HasForeignKey("PostId") // Field Name.
    );

// Adding data to Many to Many Join Table 'PostsTags' now works because of just the 2 lines below.
// See: https://github.com/dotnet/efcore/issues/23703#issuecomment-758801618
modelBuilder.Entity<Post>().Navigation(e => e.TagsInPostData).HasField("tagsInPostData_");
modelBuilder.Entity<Tag>().Navigation(e => e.PostsInTagData).HasField("postsInTagData_");

(部分) blog.proto ,完整源代碼在這里

message Post { // For Public Access
    int32 post_id = 1;
    int32 author_id = 2;
    string title = 3;
    string date_created = 4; // DateTime (UTC) string because of SQLite
    PostStatus post_stat = 5; // enum
    PostExtended post_ext = 6; // one to one
    Author post_author = 7; // Post with one author, one to one
    repeated Tag tags_in_post_data = 8; // Post with many Tags
}
message Posts {
    repeated Post posts_data = 1;
}

/*
    Many to Many Tags in auto generated table "PostsTags"
    EF Core auto generates 'PostTag' table, Renaming it to 'PostsTags' is done in ApplicationDbContext
    Because of "message Post" with "repeated Tag tags_data"
    and "message Tag" with "repeated Post posts_data"
    EF Core creates "PostTag" table 'automagically'.
*/

message Tag {
    //string tag_id = 1; // Tag itself: string
    int32 tag_id = 1;
    string name = 2;
    repeated Post posts_in_tag_data = 3; // Tag with many Posts
}
message Tags {
    repeated Tag tags_data = 1;
}

錯誤:

info: 15-1-2021 14:08:44.437 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure)
      Entity Framework Core 5.0.1 initialized 'ApplicationDbContext' using provider 'Microsoft.EntityFrameworkCore.Sqlite' with options: SensitiveDataLoggingEnabled
info: 15-1-2021 14:08:45.908 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT "p"."PostId", "p"."AuthorId", "p"."DateCreated", "p"."PostStat", "p"."Title", "a"."AuthorId", "a"."DateCreated", "a"."Name", "p0"."PostId", "p0"."Content", "p0"."Ts", "t0"."PostId", "t0"."TagId", "t0"."TagId0", "t0"."Name"
      FROM "Posts" AS "p"
      INNER JOIN "Authors" AS "a" ON "p"."AuthorId" = "a"."AuthorId"
      LEFT JOIN "PostsExtented" AS "p0" ON "p"."PostId" = "p0"."PostId"
      LEFT JOIN (
          SELECT "p1"."PostId", "p1"."TagId", "t"."TagId" AS "TagId0", "t"."Name"
          FROM "PostsTags" AS "p1"
          INNER JOIN "Tags" AS "t" ON "p1"."PostId" = "t"."TagId"
      ) AS "t0" ON "p"."PostId" = "t0"."TagId"
      WHERE "p"."PostStat" = 1
      ORDER BY "p"."DateCreated" DESC, "p"."PostId", "a"."AuthorId", "p0"."PostId", "t0"."PostId", "t0"."TagId", "t0"."TagId0"
Stack overflow.
   at System.Text.UTF8Encoding.GetByteCount(System.String)
   at Google.Protobuf.CodedOutputStream.ComputeStringSize(System.String)
   at BlazorWasmGrpcBlog.Shared.Protos.Post.CalculateSize()
   at Google.Protobuf.CodedOutputStream.ComputeMessageSize(Google.Protobuf.IMessage)
   at Google.Protobuf.FieldCodec+<>c__32`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].<ForMessage>b__32_4(System.__Canon)

完整源代碼的快速鏈接:

  • blog.proto
  • ApplicationDbContext.cs
  • BlogService.cs
  • SeedData.cs

我現在可以使用下面的代碼了。 我不知道這是否是正確的方法,或者我是否可以簡化它,我還必須了解更多關於 LINQ 和映射對象的信息。

我在 Github Repo grpc-dotnet上發布了一個問題,James Newton-King 給我的答案實際上對我幫助很大:“ Protobuf 序列化程序不支持引用循環。 ”因此,我知道我必須修改我的查詢和我不應該尋找解決 gRPC 中引用循環的解決方案。

我已經使用以下代碼更新了我在 Github 的工作示例項目

(部分)/Server/Services/ BlogService.cs (完整源代碼

public override async Task<Posts> GetPosts(Empty request, ServerCallContext context)
{
    var postsQuery = await dbContext.Posts.AsSplitQuery() // trying/testing ".AsSplitQuery()"
                                                          //var postsQuery = await dbContext.Posts
        .Where(ps => ps.PostStat == PostStatus.Published)
        .Include(pa => pa.PostAuthor)
        .Include(pe => pe.PostExtended)
        .Include(tipd => tipd.TagsInPostData)
        .OrderByDescending(dc => dc.DateCreated)
        .AsNoTracking().ToListAsync();

    // The Protobuf serializer doesn't support reference loops
    // see: https://github.com/grpc/grpc-dotnet/issues/1177#issuecomment-763910215
    //var posts = new Posts();
    //posts.PostsData.AddRange(allPosts); // so this doesn't work
    //return posts

    Posts posts = new();
    foreach (var p in postsQuery)
    {
        Post post = new()
        {
            PostId = p.PostId,
            Title = p.Title,
            DateCreated = p.DateCreated,
            PostStat = p.PostStat,
            PostAuthor = p.PostAuthor,
            PostExtended = p.PostExtended,
        };

        // Just add all the tags to each post, this isn't a reference loop.
        List<Tag> tags = p.TagsInPostData.Select(t => new Tag { TagId = t.TagId }).ToList();
        post.TagsInPostData.AddRange(tags);

        // Add Post (now with tags) to posts
        posts.PostsData.Add(post);
    }
    return posts;
}

這是結果:

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM