c# - ASP.NET Core 5 Blazor WASM、gRPC、Entity Framework Core 5 : many-to-many results in stack overflow

标签 c# linq entity-framework-core blazor grpc

信息:

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

示例项目:(完整工作源代码可在 GitHub 获取)它是博客 cms 的(简单)原型(prototype),帖子和标签之间具有多对多关系。

当我尝试从 BlogService.cs 返回带有标签的帖子列表时,应用程序因“堆栈溢出”错误而停止。在我看来,它就像一个引用循环,就像使用 json 时得到的一样。但也许我错了,我不知道。 (使用 Json.Net,您需要设置 ReferenceLoopHandling = ReferenceLoopHandling.Ignore 才能进行多对多工作)

或者我可以对 LINQ 查询进行更改,以便 BlogService.cs 中的 .Include(tipd =>tipd.TagsInPostData) 仅返回每个帖子中的标签(一层深)并且不会尝试解析每个标签中的所有帖子等等。

我仍在学习 C#、EF Core、gRPC、LINQ,英语不是我的母语,所以我希望您能理解我的问题。如果没有,请说出来,我会尽力做得更好。

(部分)BlogService.cs,完整源代码here

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 将记录添加到连接表中,我要求 a question at GitHub我在 ApplicationDbContext.cs

中相应地修改了我的代码

(部分)ApplicationDbContext.cs,完整源代码 here

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,完整源代码 here

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)

完整源代码的快速链接:

最佳答案

我现在已经可以使用下面的代码来工作了。我不知道这是否是正确的方法,或者我是否可以简化它,我还必须了解有关 LINQ 和映射对象的更多信息。

我在 Github Repo grpc-dotnet 上发布了一个问题和 answer James Newton-King 给我的实际上帮助了我很多:“Protobuf 序列化器不支持引用循环。”因此,我知道我必须修改我的查询,并且我不应该寻找解决gRPC中引用循环的解决方案。

我已经更新了我的工作 sample project在 Github 上使用以下代码:

(部分)/Server/Services/BlogService.cs(完整source)

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;
}

这就是结果:

enter image description here

关于c# - ASP.NET Core 5 Blazor WASM、gRPC、Entity Framework Core 5 : many-to-many results in stack overflow,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65738224/

相关文章:

c# - 如何在 vscode 中访问 tasks.json 中的 .env 变量?

c# - 改进 O(n^2) 算法

entity-framework-core - EF Core 1.0 - Include() 生成多个查询

c# - 如何使用 ForNpgsqlUseXminAsConcurrencyToken 创建的 EF Core 并发 token

c# - EF Core - 表 '*.__EFMigrationsHistory' 不存在

c# - 使用 EventArgs/EventArgs<T> 委托(delegate)类型而不是……的事件的好处

c# - 在 ValidationMessageFor block 中显示服务器端验证消息

c# - 根据 bool 值更改单击时的按钮文本颜色

c# - Linq 从表中选择 *

c# - 创建一个匿名类型的数组