簡體   English   中英

C# EF 數組在 LINQ 中相交到 DB

[英]C# EF array intersect in LINQ to DB

我有一個代碼優先應用程序和一個表“通知”,其中“標簽”列將標簽存儲在一個字符串中,用“;”分隔。 在上下文中,我轉換為 IEnumerable,反之亦然。 插入和獲取數據時一切正常,但在一項服務中,我動態構建過濾器,通過添加謂詞,一一添加,並添加最終謂詞列表進行查詢。 現在,我有一種情況,我想按標簽過濾,例如,我想要所有帶有標簽“Tag1”和“Tag2”的通知。 我嘗試使用包含和相交,但由於無法翻譯 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());

錯誤是(在“Where”子句中“:

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.

您不能在服務器端使用 Contains() 或 Intersect(),因為 LINQ 無法在 SQL 中從字符串轉換為 IEnumerable。 改為使用按字符串字段過濾:

    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 + ","));
    }

由於您是代碼優先,我的建議是將您的標簽放在一個單獨的表中,並在通知和標簽之間建立多對多的關系。 現在這將為您節省大量工作,並且將來如果您有一個很大的填充數據庫並且您需要進行更改:

public class Notification
{
    public int Id {get; set;}
    ... // other properties

    // every Notification has zero or more Tags (many-to-many)
    public virtual ICollection<Tag> Tags {get; set;}
}

public class Tag
{
    public int Id {get; set;}
    public string Text {get; set; }
    ... // you can add other properties later

    // every Tag is used by zero or more Notifications (many-to-many)
    public virtual ICollection<Notification> Notification {get; set;}
}

因為我使用實體框架代碼優先約定,實體框架識別通知和標簽之間的多對多關系。 不需要流利的 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”等的標簽都使用相同的文本:“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();

在您的連接字符串標記方法中甚至不可能進行以下操作:

數據庫遷移后 Tag 增加了 Boolean 屬性: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();

再一次:考慮一下如果你沒有單獨的桌子,你會做多少工作

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM