Closed JeepNL closed 3 years ago
The Protobuf serializer doesn't support reference loops. You'll need to remove the loop, or change your code so that when you create the Protobuf objects, the loop is never created.
[EDIT] UPDATE (2.10 PM GMT + 1)
Please wait with a reply: I think I've found a solution! I don't know exactly what I'm doing, and I'm probably doing it wrong, but it looks like it works.
I've to do some more testing (and learning) but I'll post my solution here some time later today.
[OLD] (1.07 PM GMT + 1)
Thank you for your reply, your answer helps actually to narrow down my search for a solution (Serializer doesn't support reference loops). I hope you don't mind but I've a couple of short questions about this & gRPC related only, so I know better what to look for.
Do you mean I've to remove the 'loop' in the protobuf definition file between the protobuf messages Post
, Posts
and Tag
, Tags
(see .proto definition example above) or do you mean I need to remove the loop (.Include
) in the LINQ query? The protobuf definition Post(s)/Tag(s) autogenerates a many-to-many 'structure' ie: from this definition .NET gRPC Tooling autogenerates the .NET types and classes and EF Core autogenerates the dbContext code & tables in the database (even the Join Table which doesn't have protobuf definition). You wrote
or change your code so that when you create the Protobuf objects, the loop is never created
but all of this code is autogenerated, which is great but I can't change this I think?
In short: I hope I can solve this by learning more about EF Core/LINQ (somewhere else than here 😉) and write a better LINQ query which give me the result I want (all tags in each post) but without the loop back from Tag to Post. I still have to learn a lot about C#, EF Core and gRPC also so I hope this isn't a 'stupid question', I don't want to take up your time if this isn't related to gRPC and/or I just need to learn to write better code.
Again, thank you for your reply because because it made it clear to me that I didn't have to think about solving the reference loop in gRPC anymore but just try to modify my queries.
As I said, I've got it working now with the code below. I do not know it this can be simplified, I need to learn much more about mapping and flatten (if that's the correct phrase) objects.
If you want you can close this issue/question.
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
var posts = new Posts();
foreach (var p in postsQuery)
{
var post = new Post();
post.PostId = p.PostId;
post.Title = p.Title;
post.DateCreated = p.DateCreated;
post.PostStat = p.PostStat;
post.PostAuthor = p.PostAuthor;
post.PostExtended = p.PostExtended;
// Just add all the tags to each post, this isn't a reference loop.
foreach (var t in p.TagsInPostData)
{
var tag = new Tag();
tag.TagId = t.TagId;
post.TagsInPostData.Add(tag);
}
posts.PostsData.Add(post);
}
return posts;
}
Glad you were able to get it working!
Context:
I've created a simple (Kestrel Hosted Blazor WASM) 'Blog CMS' prototype for this question with a many to many relationship between 'Posts' and 'Tags'. When I uncomment the
.Include
line inBlogService.cs
(see below), it results in a stack overflow error as you can see in the screenshot at the end of this post. (The error message repeats 1200+ times)To me this looks like a reference loop error, like what you get with JSON if you don't include this code: with NewtonSoft.Json (JSON.NET)
.AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore)
or with System.Text.JsonReferenceLoopHandling = ReferenceLoopHandling.Ignore
Question
FYI: I created a question at SO also but haven't received any comments so far (Jan. 20th).
Code:
(part of) /Server/Services/BlogService.cs
EF Core generates the join table PostsTags 'automagically' from the protobuf contract:
(part of) /Shared/Protos/blog.proto
I've asked a question in the EF Core repo on how to add data to this PostsTags join table, and this is possible by adding the 2 lines of code below to /Server/Data/ApplicationDbContext.cs
Console Error:
Quick Links to files mentioned here (+
SeedData.cs
)