C
C#4mo ago
SwaggerLife

✅ Npgsql.PostgresException

public class CreatePostHandler(
IHttpContextAccessor contextAccessor,
DatabaseContext db,
ISender mediator,
IValidator<CreatePostCommand> validator)
: RequestHandler(contextAccessor), IRequestHandler<CreatePostCommand, Result<Post>>
{
private const string StorageContainer = "images";

public async Task<Result<Post>> Handle(CreatePostCommand request, CancellationToken cancellationToken)
{
try
{
var validationResult = await ValidateInputAsync(request, cancellationToken);
if (!validationResult.IsValid)
return Result<Post>.Failure("Invalid input received.",
validationResult.ErrorMessages(), 400);

var newBlobName = $"{Guid.NewGuid()}{request.File.GetExtensionType()}";
var uploadResult = await UploadFileAsync(newBlobName, request.File, cancellationToken);

if (!uploadResult.IsSuccessful)
return uploadResult.ToResult(_ => new Post());

var uri = uploadResult.Data;
var nsfwResult = await ClassifyFileAsync(uri, cancellationToken);
if (!nsfwResult.IsSuccessful)
{
await DeleteNewlyUploadedFileAsync(newBlobName, cancellationToken);
return nsfwResult.ToResult<Post>(default!);
}

var post = new Post
{
Caption = request.Caption,
Url = uri.ToString(),
AuthorId = UserId
};

return await CreatePostAsync(post, cancellationToken);
}
catch (Exception e)
{
return Result<Post>.Failure("Something unexpected occurred.", [e.ToString()], 500);
}
}

private async Task<ValidationResult> ValidateInputAsync(CreatePostCommand request,
CancellationToken cancellationToken)
{
return await validator.ValidateAsync(request, cancellationToken);
}

private async Task DeleteNewlyUploadedFileAsync(string blobName, CancellationToken cancellationToken)
{
await mediator.Send(
new DeleteFileFromStorageCommand(blobName, StorageContainer),
cancellationToken);
}

private async Task<Result<Uri>> UploadFileAsync(string blobName, IFormFile file,
CancellationToken cancellationToken)
{
return await mediator.Send(new UploadFileToStorageCommand
{
ContainerName = StorageContainer,
BlobName = blobName,
ContentType = file.ContentType,
Stream = file.OpenReadStream()
}, cancellationToken);
}

private async Task<Result> ClassifyFileAsync(Uri uri, CancellationToken cancellationToken)
{
return await mediator.Send(new ClassifyImageCommand(uri), cancellationToken);
}

private async Task<Result<Post>> CreatePostAsync(Post post, CancellationToken cancellationToken)
{
var hashtags = await mediator.Send(new ExtractHashtagsCommand(post.Caption), cancellationToken);
foreach (var hashtag in hashtags)
post.Hashtags.Add(hashtag);

db.Posts.Add(post);
await db.SaveChangesAsync(cancellationToken);
return Result<Post>.Success(post);
}
}
public class CreatePostHandler(
IHttpContextAccessor contextAccessor,
DatabaseContext db,
ISender mediator,
IValidator<CreatePostCommand> validator)
: RequestHandler(contextAccessor), IRequestHandler<CreatePostCommand, Result<Post>>
{
private const string StorageContainer = "images";

public async Task<Result<Post>> Handle(CreatePostCommand request, CancellationToken cancellationToken)
{
try
{
var validationResult = await ValidateInputAsync(request, cancellationToken);
if (!validationResult.IsValid)
return Result<Post>.Failure("Invalid input received.",
validationResult.ErrorMessages(), 400);

var newBlobName = $"{Guid.NewGuid()}{request.File.GetExtensionType()}";
var uploadResult = await UploadFileAsync(newBlobName, request.File, cancellationToken);

if (!uploadResult.IsSuccessful)
return uploadResult.ToResult(_ => new Post());

var uri = uploadResult.Data;
var nsfwResult = await ClassifyFileAsync(uri, cancellationToken);
if (!nsfwResult.IsSuccessful)
{
await DeleteNewlyUploadedFileAsync(newBlobName, cancellationToken);
return nsfwResult.ToResult<Post>(default!);
}

var post = new Post
{
Caption = request.Caption,
Url = uri.ToString(),
AuthorId = UserId
};

return await CreatePostAsync(post, cancellationToken);
}
catch (Exception e)
{
return Result<Post>.Failure("Something unexpected occurred.", [e.ToString()], 500);
}
}

private async Task<ValidationResult> ValidateInputAsync(CreatePostCommand request,
CancellationToken cancellationToken)
{
return await validator.ValidateAsync(request, cancellationToken);
}

private async Task DeleteNewlyUploadedFileAsync(string blobName, CancellationToken cancellationToken)
{
await mediator.Send(
new DeleteFileFromStorageCommand(blobName, StorageContainer),
cancellationToken);
}

private async Task<Result<Uri>> UploadFileAsync(string blobName, IFormFile file,
CancellationToken cancellationToken)
{
return await mediator.Send(new UploadFileToStorageCommand
{
ContainerName = StorageContainer,
BlobName = blobName,
ContentType = file.ContentType,
Stream = file.OpenReadStream()
}, cancellationToken);
}

private async Task<Result> ClassifyFileAsync(Uri uri, CancellationToken cancellationToken)
{
return await mediator.Send(new ClassifyImageCommand(uri), cancellationToken);
}

private async Task<Result<Post>> CreatePostAsync(Post post, CancellationToken cancellationToken)
{
var hashtags = await mediator.Send(new ExtractHashtagsCommand(post.Caption), cancellationToken);
foreach (var hashtag in hashtags)
post.Hashtags.Add(hashtag);

db.Posts.Add(post);
await db.SaveChangesAsync(cancellationToken);
return Result<Post>.Success(post);
}
}
Any reasons as to why I'm getting this error?
16 Replies
SwaggerLife
SwaggerLife4mo ago
Microsoft.EntityFrameworkCore.Update[10000]
An exception occurred in the database while saving changes for context type 'Quotifyr.DatabaseContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Npgsql.PostgresException (0x80004005): 23505: duplicate key value violates unique constraint "PK_Hashtags"
Microsoft.EntityFrameworkCore.Update[10000]
An exception occurred in the database while saving changes for context type 'Quotifyr.DatabaseContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Npgsql.PostgresException (0x80004005): 23505: duplicate key value violates unique constraint "PK_Hashtags"
The hashtags are getting created. When applying the hashtags to the post. It's giving me this error.
tera
tera4mo ago
it says it right there
duplicate key value violates unique constraint "PK_Hashtags"
you're trying to create a hashtag with a primary key that already exists
SwaggerLife
SwaggerLife4mo ago
I'm not trying to create the hashtags though. The relationship between Post and Hashtag is many to many. I just want to create an entry in the joining table? Or have I misunderstood something?
tera
tera4mo ago
post.Hashtags.Add(hashtag);
...
await db.SaveChangesAsync(cancellationToken);
post.Hashtags.Add(hashtag);
...
await db.SaveChangesAsync(cancellationToken);
it will create a hashtag if hashtag is an untracked entity
SwaggerLife
SwaggerLife4mo ago
I see, is there a way I can tell it not to create?
tera
tera4mo ago
you'd need to fetch those hashtags right there from the same dbcontext where you want to add them to the posts
var hashtags = await mediator.Send(new ExtractHashtagsCommand(post.Caption), cancellationToken);
var hashtags = await mediator.Send(new ExtractHashtagsCommand(post.Caption), cancellationToken);
this is no bueno, returning entities from somewhere, and then adding them to a different dbcontext
SwaggerLife
SwaggerLife4mo ago
The think is that I don't want to rewrite the same code multiple times. There has to be a better way. At least I now know what the issue is. Thanks mate.
tera
tera4mo ago
there is always a way 😄 but might take a bit of reorganizing/refactoring np
tera
tera4mo ago
not everything is relatable/needed but good to know the on a base level
SwaggerLife
SwaggerLife4mo ago
@tera
private async Task<Result<Post>> CreatePostAsync(Post post, CancellationToken cancellationToken)
{
var hashtags = await mediator.Send(new ExtractHashtagsCommand(post.Caption), cancellationToken);
foreach (var hashtag in hashtags)
{
db.Hashtags.Attach(hashtag);
post.Hashtags.Add(hashtag);
}

db.Posts.Add(post);
await db.SaveChangesAsync(cancellationToken);
return Result<Post>.Success(post);
}
private async Task<Result<Post>> CreatePostAsync(Post post, CancellationToken cancellationToken)
{
var hashtags = await mediator.Send(new ExtractHashtagsCommand(post.Caption), cancellationToken);
foreach (var hashtag in hashtags)
{
db.Hashtags.Attach(hashtag);
post.Hashtags.Add(hashtag);
}

db.Posts.Add(post);
await db.SaveChangesAsync(cancellationToken);
return Result<Post>.Success(post);
}
Let's see if attach solves anything. Remmended by AI :kekw: Yep it fixed it :A_pepelaugh:
tera
tera4mo ago
using .Attach is a whole another can of worms, you probably should not be using it generally cos it usually solves code smell thats caused by something else so better fix root cause
SwaggerLife
SwaggerLife4mo ago
Damn, I was so happy before
using .Attach is a whole another can of worms, you probably should not be using it generally
using .Attach is a whole another can of worms, you probably should not be using it generally
Let me see if there is a better way.
tera
tera4mo ago
well the only way is to refactor the code to fetch those hashtags right there in that method/from the same dbcontext you add them to
SwaggerLife
SwaggerLife4mo ago
Yeah I will try that. Thanks for your help mate. I really appreciate it.
tera
tera4mo ago
no worries