Breaking a cycle (Post > Blog > Post > Blog > ...)

Hey fellows! I stumbled into a cycle and don't know how to break it. It occurs between posts and blogs. This is the controller action:
[HttpPost("/blog/{blogSlug}/post")]
public async Task<Post> CreatePost([FromBody] PostInput postInput, [FromRoute] string blogSlug)
{
User user = await _dataContext.Users.FirstAsync((u) => u.Slug == "timo");
Blog blog = await _dataContext.Blogs.FirstAsync((b) => b.Slug == blogSlug);
Post newPost = PostMapper.ToPost(postInput, user: user, blog: blog);
_dataContext.Posts.Add(newPost);
await _dataContext.SaveChangesAsync();

return newPost;
}
[HttpPost("/blog/{blogSlug}/post")]
public async Task<Post> CreatePost([FromBody] PostInput postInput, [FromRoute] string blogSlug)
{
User user = await _dataContext.Users.FirstAsync((u) => u.Slug == "timo");
Blog blog = await _dataContext.Blogs.FirstAsync((b) => b.Slug == blogSlug);
Post newPost = PostMapper.ToPost(postInput, user: user, blog: blog);
_dataContext.Posts.Add(newPost);
await _dataContext.SaveChangesAsync();

return newPost;
}
How to break the cycle? Thanks.
20 Replies
Angius
Angius9mo ago
.Select() Instead of fetching everything from a user, and everything from a blog, select only what's needed Not sure what PostMapper.ToPost() does
Timo Martinson
Timo MartinsonOP9mo ago
public class PostMapper
{
public static Post ToPost(PostInput postInput, User user, Blog blog)
{
return new Post(name: postInput.Name, slug: postInput.Slug, content: postInput.Content, user: user, blog: blog);
}
}
public class PostMapper
{
public static Post ToPost(PostInput postInput, User user, Blog blog)
{
return new Post(name: postInput.Name, slug: postInput.Slug, content: postInput.Content, user: user, blog: blog);
}
}
Angius
Angius9mo ago
Not sure what would cause a cycle here, tbh Do you have lazy loading enabled, with vitual properties or something? Also, are you using the built-in Identity for users, or did you build your own system? If the former, you should probably just get the user ID from claims and use that to create the post, instead of getting the user from the database By a hardcoded name, no less
Timo Martinson
Timo MartinsonOP9mo ago
the hardcoded name was just for testing purposes this is the error message:
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Id.
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Blog.Posts.Id.
when accessing all post-routes: /blog/.../post
Angius
Angius9mo ago
You want to return data in a form that doesn't cause cycles, then Use a DTO
Timo Martinson
Timo MartinsonOP9mo ago
I am using them already.
Angius
Angius9mo ago
For the input Not for the output
Timo Martinson
Timo MartinsonOP9mo ago
aaaha how would you approach it ?
Angius
Angius9mo ago
I probably wouldn't be returning the blogpost back, just an Ok if it was created succesfully, Unauthorized if user cannot be found, and NotFound if the blog is not foud Alternatively, I would return a CreatedAtAction() Othwerwise... just make a DTO
[HttpPost("/blog/{blogSlug}/post")]
- public async Task<Post> CreatePost([FromBody] PostInput postInput, [FromRoute] string blogSlug)
+ public async Task<PostDto> CreatePost([FromBody] PostInput postInput, [FromRoute] string blogSlug)****
{
User user = await _dataContext.Users.FirstAsync((u) => u.Slug == "timo");
Blog blog = await _dataContext.Blogs.FirstAsync((b) => b.Slug == blogSlug);
Post newPost = PostMapper.ToPost(postInput, user: user, blog: blog);
_dataContext.Posts.Add(newPost);
await _dataContext.SaveChangesAsync();

- return newPost;
+ return new PostDto {
+ Title = newPost.Title,
+ Slug = newPost.Slug,
+ // ...
+ };
}
[HttpPost("/blog/{blogSlug}/post")]
- public async Task<Post> CreatePost([FromBody] PostInput postInput, [FromRoute] string blogSlug)
+ public async Task<PostDto> CreatePost([FromBody] PostInput postInput, [FromRoute] string blogSlug)****
{
User user = await _dataContext.Users.FirstAsync((u) => u.Slug == "timo");
Blog blog = await _dataContext.Blogs.FirstAsync((b) => b.Slug == blogSlug);
Post newPost = PostMapper.ToPost(postInput, user: user, blog: blog);
_dataContext.Posts.Add(newPost);
await _dataContext.SaveChangesAsync();

- return newPost;
+ return new PostDto {
+ Title = newPost.Title,
+ Slug = newPost.Slug,
+ // ...
+ };
}
Timo Martinson
Timo MartinsonOP9mo ago
ah ok nice @ZZZZZZZZZZZZZZZZZZZZZZZZZ could you provide me a snippet that shows how to use required props instead of constructors? Just as you pointed out, yesterday.
Angius
Angius9mo ago
You use them just like normal properties, with initializer syntax
public class Foo
{
public required string Bar { get; init; }
}

var f = new Foo {
Bar = "unga bunga"
};
public class Foo
{
public required string Bar { get; init; }
}

var f = new Foo {
Bar = "unga bunga"
};
Timo Martinson
Timo MartinsonOP9mo ago
nice thank you and what's the story behind "init" ?
Angius
Angius9mo ago
It can only be set in the initializer or constructor, and cannot be changed after Essentially, immutability
public class Foo
{
public required string Bar { get; init; }
public required string Baz { get; set; }
}

var f = new Foo {
Bar = "unga bunga",
Baz = "foo bar",
};

f.Bar = "abc"; // not allowed
f.Baz = "abc"; // allowed
public class Foo
{
public required string Bar { get; init; }
public required string Baz { get; set; }
}

var f = new Foo {
Bar = "unga bunga",
Baz = "foo bar",
};

f.Bar = "abc"; // not allowed
f.Baz = "abc"; // allowed
Timo Martinson
Timo MartinsonOP9mo ago
and you skip the constructor stuff entirely? if I want to use Entity Framework ?
Angius
Angius9mo ago
As you can see in the example, no constructor was needed
Timo Martinson
Timo MartinsonOP9mo ago
cool
MODiX
MODiX9mo ago
Angius
REPL Result: Success
public class Foo
{
public required string Bar { get; init; }
public required string Baz { get; set; }
}

var f = new Foo {
Bar = "unga bunga",
Baz = "foo bar",
};

f
public class Foo
{
public required string Bar { get; init; }
public required string Baz { get; set; }
}

var f = new Foo {
Bar = "unga bunga",
Baz = "foo bar",
};

f
Result: Foo
{
"bar": "unga bunga",
"baz": "foo bar"
}
{
"bar": "unga bunga",
"baz": "foo bar"
}
Compile: 312.851ms | Execution: 44.459ms | React with ❌ to remove this embed.
Timo Martinson
Timo MartinsonOP9mo ago
good how does it work with Entity Framework?
Angius
Angius9mo ago
It just does™ EF just uses reflections to set the properties
Timo Martinson
Timo MartinsonOP9mo ago
good
Want results from more Discord servers?
Add your server