Если вы попытаетесь найти сущности, содержащие жанры из запроса (они также могут содержать другие жанры, которых нет в запросе), вы получите ошибку. Я пробовал много вариантов, но в итоге получил 3 запроса.
Request.genres — это строка списка.
query = query.Where(g => request.genres.All(r => g.Genres.Any(gameGenre => gameGenre.Equals(new StringForGame(r)))));
System.InvalidOperationException: выражение LINQ 'r => ShapedQueryExpression: ВыражениеЗапроса: Отображение проекции: EmptyProjectionMember -> EntityProjectionExpression: StringForGame ВЫБЕРИТЕ 1 ОТ Games_Genres КАК г ShaperExpression: EntityShaperExpression: GameProfile.Domain.Entities.GameEntites.Game.Genres#StringForGame Валуебуферэкспрессион: Выражение Проеционбиндингекспрессион: Емпаспроектионмембер Обнуляемый: Ложь
.Where(namelessParameter{0} => object.Equals( objA: (объект)EF.Property(EntityShaperExpression: GameProfile.Domain.Entities.GameEntites.Game Валуебуферэкспрессион: Выражение Проеционбиндингекспрессион: Емпаспроектионмембер Обнуляемый: Ложь , "Идентификатор"), objB: (объект)EF.Property(безымянныйпараметр{0}, "GameId"))) .AsQueryable() .Any(o => o.GameString.Equals(new StringForGame(r)))' не удалось перевести.
query = query.Where(g => g.Genres.Any(gg => request.genres.All(r=> gg.GameString == r)));
System.InvalidOperationException: выражение LINQ 'r => EntityShaperExpression: GameProfile.Domain.Entities.GameEntites.Game.Genres#StringForGame Валуебуферэкспрессион: Выражение Проеционбиндингекспрессион: Емпаспроектионмембер Обнуляемый: Ложь .GameString == r' не удалось перевести.
Этот запрос работает, но не дает мне нужного результата.
query = query.Where(g => g.Genres.Any(gg => request.genres.Contains(gg.GameString)));
// and this too
query = query.Where(g => g.Genres.All(gg => request.genres.Contains(gg.GameString)));
Для контекста
public async Task<List<Game>> Handle(GetGamesQuery request, CancellationToken cancellationToken)
{
var query = _context.Games.AsQueryable();
if (request.sort == "titleAtoZ")
{
query = query.OrderBy(x => x.Title);
}
else if (request.sort == "titleZtoA")
{
query = query.OrderByDescending(x => x.Title);
}
else if (request.sort == "dateAscending")
{
query = query.OrderBy(x => x.ReleaseDate);
}
else if (request.sort == "dateDescending")
{
query = query.OrderByDescending(x => x.ReleaseDate);
}
if (request.releaseDateOf != DateTime.MinValue && request.releaseDateTo != DateTime.MinValue)
{
query = query.Where(x => x.ReleaseDate >= request.releaseDateOf && x.ReleaseDate <= request.releaseDateTo);
}
else if (request.releaseDateOf == DateTime.MinValue && request.releaseDateTo != DateTime.MinValue)
{
query = query.Where(x => x.ReleaseDate <= request.releaseDateTo);
}
else if (request.releaseDateOf != DateTime.MinValue && request.releaseDateTo == DateTime.MinValue)
{
query = query.Where(x => x.ReleaseDate >= request.releaseDateOf);
}
if (request.nsfw == "yes")
{
query = query.Where(x => x.Nsfw == true);
}
else if (request.nsfw == "no")
{
query = query.Where(x => x.Nsfw == false);
}
if (request.genres is not null && request.genres.Count > 0)
{
query = query.Where(g => g.Genres.Any(gg => request.genres.Contains(gg.GameString)));
//query = query.Where(g => g.Genres.Any(gg => request.genres.All(r=> gg.GameString == r)));
//query = query.Where(g => request.genres.All(r => g.Genres.Any(gameGenre => gameGenre.Equals(new StringForGame(r)))));
}
int skipGame = request.page * 50;
query = query.Skip(skipGame).Take(50);
var games = await query.ToListAsync(cancellationToken);
return games;
}
public sealed class Game : Entity
{
public Game(Guid id,
string title,
DateTime releaseDate,
Uri headerImage,
bool nsfw,
string description,
ICollection<StringForGame> developers,
ICollection<StringForGame> publishers,
ICollection<StringForGame> genres,
ICollection<UriForGame> screenshots,
ICollection<UriForGame> shopsLinkBuyGame,
int achievementsCount) : this(id,title,releaseDate,headerImage,nsfw,description,achievementsCount)
{
Developers = developers;
Publishers = publishers;
Genres = genres;
Screenshots = screenshots;
ShopsLinkBuyGame = shopsLinkBuyGame;
}
/// <summary>
/// EF constructor
/// </summary>
private Game(Guid id,
string title,
DateTime releaseDate,
Uri headerImage,
bool nsfw,
string description,
int achievementsCount) : base(id)
{
Title = title;
ReleaseDate = releaseDate;
HeaderImage = headerImage;
Nsfw = nsfw;
Description = description;
AchievementsCount = achievementsCount;
}
public string Title { get; private set; }
public DateTime ReleaseDate { get; private set; }
public Uri HeaderImage { get; private set; }
public bool Nsfw { get; private set; }
public string Description { get; private set; }
public ICollection<UriForGame> Screenshots { get; private set; }
public ICollection<StringForGame> Genres { get; private set; }
}
public sealed class StringForGame : ValueObject
{
public StringForGame(string gameString)
{
GameString = gameString;
}
public string GameString { get; private init; }
public override IEnumerable<object> GetAtomicValues()
{
yield return GameString;
}
}
public abstract class ValueObject : IEquatable<ValueObject>
{
public abstract IEnumerable<object> GetAtomicValues();
public bool Equals(ValueObject? other)
{
return other is not null && ValuesAreEqual(other);
}
public override bool Equals(object? obj)
{
return obj is ValueObject valueObject && ValuesAreEqual(valueObject);
}
public override int GetHashCode()
{
return GetAtomicValues().Aggregate(default(int), HashCode.Combine);
}
private bool ValuesAreEqual(ValueObject other)
{
return GetAtomicValues().SequenceEqual(other.GetAtomicValues());
}
}
Я действительно не понимаю, в чем проблема, и любая помощь будет принята с благодарностью.
Используемая версия: EF Core 7.0.8
Относительно хорошо известно, что единственным поддерживаемым методом для коллекций в памяти внутри запроса LINQ to Entities во всех версиях EF («классическая» и Core) является Contains
.
Что отлично работает с критериями типа Any
, такими как
query.Any(x => collection.Contains(Field(x)))
но не для критериев типа All
, таких как
query.All(x => collection.Contains(Field(x)))
скорее всего, из-за отсутствия соответствующей конструкции SQL (хотя они делают это для некоторых других выражений LINQ).
И обычный (и единственный известный) обходной путь — использовать «подсчет совпадений», эквивалентный LINQ All
query.Count(x => collection.Contains(Field(x))) == collection.Count()
(в некоторых версиях вам может понадобиться сохранить collection.Count()
в переменной вне запроса и использовать эту переменную внутри).
С учетом сказанного, решение в вашем случае что-то вроде
query = query.Where(g =>
g.Genres.Count(gg => request.genres.Contains(gg.GameString)) == request.Genres.Count()
);