C# Массив EF пересекается в LINQ to DB - PullRequest
0 голосов
/ 08 июля 2020

У меня есть приложение с первым кодом и одна таблица «Уведомление» со столбцом «Теги», в которой теги хранятся в одной строке, разделенной ';'. В контексте у меня есть преобразование в IEnumerable и наоборот. Все это хорошо работает при вставке и получении данных, но в одной службе я создаю фильтр динамически, добавляя предикаты один за другим и добавляя окончательный список предикатов в запрос. Теперь у меня есть ситуация, когда я хочу фильтровать по тегам, например, мне нужны все уведомления с тегами «Tag1» и «Tag2». Я пробовал использовать Contains и Intersect, но постоянно получаю исключения, потому что выражение LINQ не может быть передано. Любые идеи? Спасибо.

Контекст:

    builder.Entity<Notification>().Property(x => x.Tags).HasConversion
                    (x => string.Join(';', x),
                    x => x.Split(';', StringSplitOptions.RemoveEmptyEntries)
);

Сервис:

var filter = PredicateBuilder.True<UserNotification>();
IEnumerable<string> tagsFilter = new List<string>() { "Tag1","Tag2" };
filter = filter.And(x => x.Notification.Tags != null); // this line works

// both these lines fail (they are here as alternatives, should give the same result)
filter = filter.And(x => x.Notification.Tags.Any(r => tagsFilter.Contains(r)));
filter = filter.And(x => x.Notification.Tags.Intersect(tagsFilter).Any());

Ошибка (в разделе «Где»:

System.InvalidOperationException: The LINQ expression 'DbSet<UserNotification>
    .Join(
        outer: DbSet<Notification>, 
        inner: u => EF.Property<Nullable<long>>(u, "NotificationId"), 
        outerKeySelector: n => EF.Property<Nullable<long>>(n, "Id"), 
        innerKeySelector: (o, i) => new TransparentIdentifier<UserNotification, Notification>(
            Outer = o, 
            Inner = i
        ))
    .Where(u => True && __statuses_0
        .Contains(u.Outer.NotificationStatus) && __types_1
        .Contains(u.Inner.Type) && u.Inner.Tags != null && u.Inner.Tags
        .Any(r => __tags2_2.Contains(r)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

Ответы [ 2 ]

0 голосов
/ 15 июля 2020

Поскольку вы работаете с кодом, я бы посоветовал поместить ваши теги в отдельную таблицу и иметь отношение «многие ко многим» между уведомлениями и тегами. Это сэкономит вам много работы сейчас и в будущем, если у вас большая заполненная база данных и вам нужно внести изменения: связь "многие ко многим" между уведомлениями и тегами. Не требуется ни API-интерфейса, ни атрибутов.

Требование : Учитывая текст нескольких тегов, дайте мне все уведомления со всеми этими тегами:

IEnumerable<string> tagTexts = ...
var notifications = dbContext.Tags.Where(tag => tagTexts.Contains(tag.Text)
    .SelectMany(tag => tag.Notifications)
    .Distinct();

Требование Удалить все уведомления, в которых есть только теги с текстом «SQL»

string tagText = "SQL";
var notificationsWithOnlyTagSQL = dbContext.Notifications
    .Where(notification => notification.Tags.All(tag => tag.TagText == tagText)
    .ToList();
dbContext.Notifications.RemoveRange(notificationsWithTagSQL);

Требование Убедитесь, что все теги с «sql», «SQL "," Sql ", et c используйте тот же текст:" SQL "(предположим, у вас есть база данных без учета регистра)

const string proposedTagText = "SQL";
var tagsToChange = dbContext.Tags.Where(tag => tag.TagText == proposedTagText).ToList();

foreach (var tag in tagsToChange)
{
    tag.TagText = proposedTagText;
}
dbContext.SaveChanges();

Посмотрите, насколько это проще, если у вас отдельная таблица тегов! Подумайте, сколько работы было бы, если бы вам пришлось проверять строки каждого уведомления!

О боже, теперь, когда мы изменили тег «sql» на «SQL», у нас есть несколько теги с одинаковым TagText. Убедитесь, что остался только один:

var tagsSql = dbContext.Tags
    .Where(tag => tag.TagText == proposedTagText)
    .ToList();
var tagToKeep = tagsSql.FirstOrDefault();
var tagsToRemove = tagsSql.Skip(1).ToList();

var notificationsToChange = dbContext.Tags
    .Where(tag => tagIdsToRemove.Contains(tag))
    .SelectMany(tag => tag.Notifications)
    .Distinct();

foreach (var notification in notificationsToChange)
{
    // remove all tagsToRemove from this notification
    notification.Tags.RemoveRange(tagsToRemove);

    // if this notification does not have tagToKeep, add it:
    if (!notification.Contains(tagToKeep))
    {
        notification.Add(tagToKeep);
    }
}

// now that no one uses TagsToRemove anymore, we can remove the tags:
dbContext.Tags.RemoveRange(tagsToRemove);
dbContext.SaveChanges();

Следующее даже невозможно в вашем методе конкатенированного строкового тега:

После переноса базы данных Тег имеет добавленное логическое свойство: IsObsolete, изначально установлено значение false.

Требование Дайте мне все уведомления с устаревшими тегами:

var notificationsWithObsoleteTags= dbContext.Tags
    .Where(tag => tag.IsObsolete)
    .SelectMany(tag => tag.Notifications);

Требование : удалите все устаревшие теги из ваших уведомлений

var obsoleteTags = dbContext.Tags.Where(tag => tag.IsObsolete).ToList();
dbContext.RemoveRange(obsoleteTags);
dbContext.SaveChanges();

Опять же: подумайте, сколько работы вам пришлось бы сделать, если бы у вас не было отдельной таблицы

0 голосов
/ 09 июля 2020

Вы не можете использовать Contains () или Intersect () на стороне сервера, потому что LINQ не может преобразовать из строки в IEnumerable в SQL. Вместо этого используйте фильтр по строковому полю:

    var filter = PredicateBuilder.True<UserNotification>();
    IEnumerable<string> tagsFilter = new List<string>() { "Tag1","Tag2" };
    filter = filter.And(x => x.Notification.Tags != null); // this line works
    
    foreach (var tag in tagsFilter) {
        // search for tag with heading and trailing ',' to distinct tags 'ham' and 'hamburger'
        filter = filter.And(x => ("," + x.Notification.Tags + ",").Contains("," + tag + ","));
    }
...