C
C#11mo ago
Sinkarq

❔ EF Core CosmosDB cannot translate AnyAsync method

Pictures:
15 Replies
Sinkarq
Sinkarq11mo ago
using System.Net;
using JShop.Core;
using JShop.Core.Repositories;
using JShop.Shared.Contracts.Identity;
using JShop.Shared.Results;
using MassTransit;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace JShop.Application.Identity.Commands.RegisterUser;

public sealed class RegisterUserHandler : IConsumer<RegisterUserCommand>
{
private readonly IUsersRepository _usersRepository;
private readonly IPasswordHasher<User> _passwordHasher;

public RegisterUserHandler(IUsersRepository usersRepository, IPasswordHasher<User> passwordHasher)
{
_usersRepository = usersRepository;
_passwordHasher = passwordHasher;
}

public async Task Consume(ConsumeContext<RegisterUserCommand> context)
{
var normalizedEmail = context.Message.Email.ToUpper();

var usedEmail = await _usersRepository.AllUsers()
.FirstOrDefaultAsync(x => x.Email == normalizedEmail);

if (usedEmail is not null)
{
await context.RespondAsync(new Result(HttpStatusCode.Conflict, None.Instance));
return;
}

var user = new User
{
Email = normalizedEmail
};

user.PasswordHash = _passwordHasher.HashPassword(user, context.Message.Password);

await _usersRepository.AddUserAsync(user, context.CancellationToken);

await _usersRepository.SaveChangesAsync(context.CancellationToken);

await context.RespondAsync(new Result(HttpStatusCode.Created, None.Instance));
}
}
using System.Net;
using JShop.Core;
using JShop.Core.Repositories;
using JShop.Shared.Contracts.Identity;
using JShop.Shared.Results;
using MassTransit;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace JShop.Application.Identity.Commands.RegisterUser;

public sealed class RegisterUserHandler : IConsumer<RegisterUserCommand>
{
private readonly IUsersRepository _usersRepository;
private readonly IPasswordHasher<User> _passwordHasher;

public RegisterUserHandler(IUsersRepository usersRepository, IPasswordHasher<User> passwordHasher)
{
_usersRepository = usersRepository;
_passwordHasher = passwordHasher;
}

public async Task Consume(ConsumeContext<RegisterUserCommand> context)
{
var normalizedEmail = context.Message.Email.ToUpper();

var usedEmail = await _usersRepository.AllUsers()
.FirstOrDefaultAsync(x => x.Email == normalizedEmail);

if (usedEmail is not null)
{
await context.RespondAsync(new Result(HttpStatusCode.Conflict, None.Instance));
return;
}

var user = new User
{
Email = normalizedEmail
};

user.PasswordHash = _passwordHasher.HashPassword(user, context.Message.Password);

await _usersRepository.AddUserAsync(user, context.CancellationToken);

await _usersRepository.SaveChangesAsync(context.CancellationToken);

await context.RespondAsync(new Result(HttpStatusCode.Created, None.Instance));
}
}
namespace JShop.Core.Repositories;

public interface IUsersRepository
{
public IQueryable<User> AllUsers();

public Task AddUserAsync(User user, CancellationToken cancellationToken = default);

public Task<User?> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default);

Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
namespace JShop.Core.Repositories;

public interface IUsersRepository
{
public IQueryable<User> AllUsers();

public Task AddUserAsync(User user, CancellationToken cancellationToken = default);

public Task<User?> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default);

Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
using JShop.Core;
using JShop.Core.Repositories;
using Microsoft.EntityFrameworkCore;

namespace JShop.Infrastructure.Repositories;

internal sealed class UsersRepository : IUsersRepository
{
private readonly DatabaseContext _databaseContext;

public UsersRepository(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}

public IQueryable<User> AllUsers()
{
return _databaseContext.Users.AsQueryable();
}

public async Task AddUserAsync(User user, CancellationToken cancellationToken = default)
{
_databaseContext.Users.Add(user);

await _databaseContext.SaveChangesAsync(cancellationToken);
}

public async Task<User?> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default)
{
var user = await _databaseContext.Users.Where(x => x.Email == email.ToUpper())
.FirstOrDefaultAsync(cancellationToken);

return user;
}

public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
return await _databaseContext.SaveChangesAsync(cancellationToken);
}
}
using JShop.Core;
using JShop.Core.Repositories;
using Microsoft.EntityFrameworkCore;

namespace JShop.Infrastructure.Repositories;

internal sealed class UsersRepository : IUsersRepository
{
private readonly DatabaseContext _databaseContext;

public UsersRepository(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}

public IQueryable<User> AllUsers()
{
return _databaseContext.Users.AsQueryable();
}

public async Task AddUserAsync(User user, CancellationToken cancellationToken = default)
{
_databaseContext.Users.Add(user);

await _databaseContext.SaveChangesAsync(cancellationToken);
}

public async Task<User?> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default)
{
var user = await _databaseContext.Users.Where(x => x.Email == email.ToUpper())
.FirstOrDefaultAsync(cancellationToken);

return user;
}

public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
return await _databaseContext.SaveChangesAsync(cancellationToken);
}
}
using Microsoft.EntityFrameworkCore;
using User = JShop.Core.User;

namespace JShop.Infrastructure;

public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions options) : base(options)
{

}

public DatabaseContext()
{

}

public DbSet<User> Users { get; set; } = null!;

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured)
{
return;
}

optionsBuilder.UseCosmos(
"https://localhost:8081",
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
databaseName: "JShop");
}

private const string UsersContainerName = "Users";

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasPartitionKey(x => x.Id)
.ToContainer(UsersContainerName);
}
}
using Microsoft.EntityFrameworkCore;
using User = JShop.Core.User;

namespace JShop.Infrastructure;

public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions options) : base(options)
{

}

public DatabaseContext()
{

}

public DbSet<User> Users { get; set; } = null!;

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured)
{
return;
}

optionsBuilder.UseCosmos(
"https://localhost:8081",
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
databaseName: "JShop");
}

private const string UsersContainerName = "Users";

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasPartitionKey(x => x.Id)
.ToContainer(UsersContainerName);
}
}
Jimmacle
Jimmacle11mo ago
that looks like an API key you shouldn't have leaked
Sinkarq
Sinkarq11mo ago
it's for the local emulator I guess it isn't a big deal
Jimmacle
Jimmacle11mo ago
i don't see an AnyAsync anywhere unless i'm blind
Sinkarq
Sinkarq11mo ago
oops wrong code
using System.Net;
using JShop.Core;
using JShop.Core.Repositories;
using JShop.Shared.Contracts.Identity;
using JShop.Shared.Results;
using MassTransit;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace JShop.Application.Identity.Commands.RegisterUser;

public sealed class RegisterUserHandler : IConsumer<RegisterUserCommand>
{
private readonly IUsersRepository _usersRepository;
private readonly IPasswordHasher<User> _passwordHasher;

public RegisterUserHandler(IUsersRepository usersRepository, IPasswordHasher<User> passwordHasher)
{
_usersRepository = usersRepository;
_passwordHasher = passwordHasher;
}

public async Task Consume(ConsumeContext<RegisterUserCommand> context)
{
var normalizedEmail = context.Message.Email.ToUpper();

var usedEmail = await _usersRepository.AllUsers()
.AnyAsync(x => x.Email == normalizedEmail);

if (usedEmail)
{
await context.RespondAsync(new Result(HttpStatusCode.Conflict, None.Instance));
return;
}

var user = new User
{
Email = normalizedEmail
};

user.PasswordHash = _passwordHasher.HashPassword(user, context.Message.Password);

await _usersRepository.AddUserAsync(user, context.CancellationToken);

await _usersRepository.SaveChangesAsync(context.CancellationToken);

await context.RespondAsync(new Result(HttpStatusCode.Created, None.Instance));
}
}
using System.Net;
using JShop.Core;
using JShop.Core.Repositories;
using JShop.Shared.Contracts.Identity;
using JShop.Shared.Results;
using MassTransit;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace JShop.Application.Identity.Commands.RegisterUser;

public sealed class RegisterUserHandler : IConsumer<RegisterUserCommand>
{
private readonly IUsersRepository _usersRepository;
private readonly IPasswordHasher<User> _passwordHasher;

public RegisterUserHandler(IUsersRepository usersRepository, IPasswordHasher<User> passwordHasher)
{
_usersRepository = usersRepository;
_passwordHasher = passwordHasher;
}

public async Task Consume(ConsumeContext<RegisterUserCommand> context)
{
var normalizedEmail = context.Message.Email.ToUpper();

var usedEmail = await _usersRepository.AllUsers()
.AnyAsync(x => x.Email == normalizedEmail);

if (usedEmail)
{
await context.RespondAsync(new Result(HttpStatusCode.Conflict, None.Instance));
return;
}

var user = new User
{
Email = normalizedEmail
};

user.PasswordHash = _passwordHasher.HashPassword(user, context.Message.Password);

await _usersRepository.AddUserAsync(user, context.CancellationToken);

await _usersRepository.SaveChangesAsync(context.CancellationToken);

await context.RespondAsync(new Result(HttpStatusCode.Created, None.Instance));
}
}
here it is
Jimmacle
Jimmacle11mo ago
does this part of the code you posted work?
var usedEmail = await _usersRepository.AllUsers()
.FirstOrDefaultAsync(x => x.Email == normalizedEmail);
var usedEmail = await _usersRepository.AllUsers()
.FirstOrDefaultAsync(x => x.Email == normalizedEmail);
Sinkarq
Sinkarq11mo ago
yes, as expected but when I try to use AnyAsync it throws exception
Jimmacle
Jimmacle11mo ago
that suggests the db provider doesn't implement a translation for AnyAsync i've never used cosmosdb so i wouldn't know for sure
Angius
Angius11mo ago
What db are you using, and what does _userRepository.AllUsers() return?
Jimmacle
Jimmacle11mo ago
it returns _databaseContext.Users.AsQueryable();
Sinkarq
Sinkarq11mo ago
CosmosDB, it returns IQueryable from the DbSet of the Users
Jimmacle
Jimmacle11mo ago
bad way to write a repository but that's another topic
Angius
Angius11mo ago
Well, the repository at least doesn't do anything special, it's just useless passthrough methods
Sinkarq
Sinkarq11mo ago
right now you are both right, I put the repository so if ef core (first time tryting it for cosmosdb) makes the developer experience bad I would switch to the default CosmosDb SDK EF Core DBContext implements the repository pattern to some extend by itself but that's another topic
Accord
Accord11mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server
More Posts