I don’t know why, when I execute this LINQ request, this exception is returned :
System.InvalidOperationException: The LINQ expression '__ids_0
.Contains(StructuralTypeShaperExpression:
Patron.Domain.RoleAggregate.Role
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
.Id.Value)' could not be translated.
That is the code :
public async Task<List<Role>> GetRolesByIdsAsync(List<RoleId> roleIds)
{
var ids = roleIds.Select(id => id.Value).ToList();
return await dbContext.Roles
.Where(r => ids.Contains(r.Id.Value))
.ToListAsync();
}
RoleId definition :
public sealed class RoleId : AggregateRootId<Guid>
{
private RoleId(Guid value) : base(value)
{
}
public static RoleId Create(Guid userId) =>
new(userId);
public static RoleId CreateUnique() =>
new(Guid.NewGuid());
public static ErrorOr<RoleId> Create(string value)
{
if (!Guid.TryParse(value, out var guid))
{
return Errors.Role.InvalidRoleId;
}
return new RoleId(guid);
}
}
AggregateRootId :
public abstract class AggregateRootId<TId> : EntityId<TId>
{
protected AggregateRootId(TId value) : base(value)
{
}
}
EntityId :
public abstract class EntityId<TId>(TId value) : ValueObject
{
public TId Value { get; } = value;
public override IEnumerable<object?> GetEqualityComponents()
{
yield return Value;
}
public override string? ToString() => Value?.ToString() ??
base.ToString();
}
I tryed with the Any() method instead of Contains() but I have the same message.
I found a solution but it’s very ugly and not optimised (I want to use LINQ) :
public async Task<List<Role>> GetListRoleByListId(List<RoleId> roleIds)
{
var roles = new List<Role>();
foreach (var role in await dbContext.Roles.ToListAsync())
{
foreach (var roleId in roleIds)
{
if (role.Id.Equals(roleId))
{
roles.Add(role);
}
}
}
return roles;
}
It’s weird because I can compare two RoleId here :
public async Task<Role?> GetAsync(RoleId id) =>
await dbContext.Roles.FirstOrDefaultAsync(r => r.Id == id);
Someone knows where this error comes from and how fix it please ?
The GitLab of the project :
https://gitlab.com/victor.nardel/training-project-ddd-clean-architecture
You can find the problem in ./Patron.Infrastructure/Persistence/Repositories/RoleRepository.cs
2
Answers
So, the only alternative that I found is to build my sql query :
If someone want to criticize my method, don't hesitate to share your opinion !
Thanks
You get an error in
.Where(r => ids.Contains(r.Id.Value))
because the data provider you are using does not know how to translateList<RoleId>.Contains(RoleId)
to a query – data store doesn’t know the C# typeRoleId
you defined in application code and the translation fails. Think about it – how could e.g. SQL check if a set of RoleIds contains a RoleId if it doesn’t even know what that type is?You’re not getting the right data from EF Core call, but rather you’re loading all of the data into memory and then perform some additional operations. You should avoid doing so because it’s way less performant from the point of execution time and memory pressure.
It’s not weird at all. It’s not comparison, it’s equality check operator. Equality only tells you if something is equal or not, while comparing actually enhances on it by telling you which one is lower/greater if not equal which is impossible to get right without type knowledge. Equality operator doesn’t need that context – it can just do reference comparison or treat instances memory as byte[] and do a SequenceEqual between them.
The easiest solution is using primitive type compare, if data store supports the type, EF Core will be able to translate Contains() among other operations. This call loads only required data so it’s way more efficient.