获取请求谓词以过滤核心数据实体,其中较小的 NSSet 包含在较大的 NSSet 中

发布于 2025-01-15 01:23:04 字数 2787 浏览 2 评论 0 原文

我正在编写一个具有 iOS 和 macOS 目标的 100% SwiftUI 应用程序,使用 Core Data 和 NSPercientCloudKitContainer 来备份到 iCloud 并在登录到同一 AppleID 的设备之间进行同步。

涉及的三个实体是:餐食、份量、食物

每份餐食具有:

  • 与份量的多对多关系
  • 与食物的多对多关系

每个份量具有:

  • 食物的多对多关系

与我正在尝试的 准备一个谓词来过滤膳食,其中每个膳食部分包含某种食物或膳食直接包含某种食物。

所以我将提供一个实际的例子...

餐 1

包括...

部分

  • 香蕉冰沙
  • 鸡蛋三明治

食物

  • 苹果

部分与名称 Banana Smoothie 包含以下食物:

  • 香蕉
  • 牛奶
  • 蜂蜜

Meal 2

包含...

部分

  • 蓝莓 Smoothie
  • 火腿三明治

食物

  • 香蕉

适用于 macOS目标,我使用相对较新的 Table 结构来呈现一个表格,其中列出了特定食品实体的所有餐食实体,包括一个或多个部分实体包含该特定食品实体的餐食实体。

如果我回头参考上面的示例,对于名为“Banana”的 Food 实体,我希望我的谓词能够过滤我的 FetchRequest ,以便名称为“Meal 1”和“Meal 1”的 Meal 实体能够被过滤。结果中包含“餐 2”。

@FetchRequest var meals: FetchedResults<Meal>

这是此 FetchRequest 的当前谓词...

let portions = NSSet(object: food.foodPortions as Any)
let predicatePartA = NSPredicate(format: "%@ IN mealFoods", food)
let predicatePartB = NSPredicate(format: "ANY %@ IN mealsPortions", portions)
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicatePartA, predicatePartB])

其中 food 是 @ObservedObject var food: Food mealFoodsmealsPortions 是来自每份 MealNSSet 多对多关系 to > 对象。

predicatePartA 工作正常,我怀疑是因为它是一个 Object IN 对象的 NSSet

predicatePartB 不会崩溃,但它也无法解决任何问题,我怀疑是因为我提供的是一组而不是单个对象。

我已经尝试研究如何实现这一点已经有一段时间了,我能想到的最好的办法是操作员...... @distinctUnionOfSets @"SUBQUERY()

...但除了这个网站< /a> 我几乎找不到关于如何实现它们的文档

UPDATE

在 @JoakimDanielson 的帮助下我尝试使用 SUBQUERY...

let predicatePartB = NSPredicate(format: "SUBQUERY(mealsPortions, $portion, $portion IN %@).@count > 0", portions)

并且

let predicatePartB = NSPredicate(format: "SUBQUERY(mealsPortions, $portion, $portion IN %@).@count != 0", portions)

这不会崩溃,但它没有提供预期的结果。有

什么建议吗?

还值得注意的是,我找到了一些更好的 文档Apple 支持此语法,但由于谓词不起作用,我仍然不确定该

init(forSubquery:usingIteratorVariable:predicate:)

语法是否正确。

SUBQUERY(collection_expression, variable_expression, predicate);

I'm writing a 100% SwiftUI app with iOS and macOS targets, using Core Data and NSPersistentCloudKitContainer to backup to iCloud and sync between devices signed into the same AppleID.

The three entities involved are: Meals, Portions, Foods

Each Meal has:

  • a many-to-many relationship with Portions
  • a many-to-many relationship with Foods

Each Portion has:

  • a many-to-many relationship with Foods

I'm attempting to prepare a predicate to filter meals where each meal portion contains a certain food OR the meal contains a certain food directly.

So I'll provide a practical example...

Meal 1

consists of...

Portions

  • Banana Smoothie
  • Egg Sandwich

Foods

  • Apple

The Portion with the name Banana Smoothie contains the following Foods:

  • Banana
  • Cows Milk
  • Honey

Meal 2

consists of...

Portions

  • Blueberry Smoothie
  • Ham Sandwich

Foods

  • Banana

For the macOS target, I'm using the relatively new Table structure to present a table that lists all Meal entities for a certain Food entity, including those Meal entities where one or more of the Portion entities contains that certain Food entity.

If I refer back to the above example, for the Food entity named "Banana", I'd want my predicate to filter my FetchRequest such that Meal entities with names "Meal 1" & "Meal 2" are in the results.

@FetchRequest var meals: FetchedResults<Meal>

Here is the current predicate for this FetchRequest...

let portions = NSSet(object: food.foodPortions as Any)
let predicatePartA = NSPredicate(format: "%@ IN mealFoods", food)
let predicatePartB = NSPredicate(format: "ANY %@ IN mealsPortions", portions)
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicatePartA, predicatePartB])

where food is @ObservedObject var food: Food
and mealFoods and mealsPortions are NSSet many-to-many relationships to from every Meal object.

predicatePartA works fine, I suspect because it is one single Object IN an NSSet of objects.

predicatePartB doesn't crash, but it also doesn't resolve any meals, I suspect because I'm providing a set instead of a single object.

I've attempted to research for some time now how this might be achieved and the best I can come up with are the operators...
@distinctUnionOfSets
@"SUBQUERY()

...but apart from this website there is little documentation I can find on how to implement them.

UPDATE

With help from @JoakimDanielson I've attempted to use SUBQUERY...

let predicatePartB = NSPredicate(format: "SUBQUERY(mealsPortions, $portion, $portion IN %@).@count > 0", portions)

AND

let predicatePartB = NSPredicate(format: "SUBQUERY(mealsPortions, $portion, $portion IN %@).@count != 0", portions)

Again this does not crash, but it does not provide the expected results for the fetch request.

Any suggestions please?

Also worth noting that I've found some better documentation by Apple that supports this syntax although, because the predicate isn't working, I still not sure it is correct.

init(forSubquery:usingIteratorVariable:predicate:)

with the syntax

SUBQUERY(collection_expression, variable_expression, predicate);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

风为裳 2025-01-22 01:23:04

简短的回答...

let portions = food.foodPortions
let predicatePartB = NSPredicate(format: "(SUBQUERY(mealsPortions, $p, $p IN %@).@count != 0)", portions!)

或者,如果我为 portions 准备一个计算属性...

var portions: NSSet {
    if let p = food.foodPortions { return p }
    return NSSet()
}

那么在创建谓词时,我不需要强制解开可选 NSSet ...

let predicatePartB = NSPredicate(format: "(SUBQUERY(mealsPortions, $p, $p IN %@).@count != 0)", portions)

大多数读这篇文章的人可能不想知道细节,但尽管如此,我还是觉得有必要把它写下来,所以长答案是...

...分为两部分,或者至少承认帮助我的两个贡献者解决它。

第 1 部分

主要是@JoakimDanielson 确认 SUBQUERY 是解决方案的正确路径,并花时间为我的案例制定语法,并质疑最终结果是一个非常基本的错误,问题不是我的 SUBQUERY 语法,而是实际上我准备在谓词字符串中使用的 NSSet 的方式。

我需要做的就是将...更改

let portions = NSSet(object: food.foodPortions as Any)

为...

let portions = food.foodPortions

之后,我可以在创建谓词时强制将其解开,或者以其他方式准备一个计算属性(我选择的解决方案) - 如上面简短答案中所详述。

这只是一个错误,因为我对集合 NSSetSet 的理解不够。回顾一下 swift.org 文档 对我有帮助。

第 2 部分

其次,这个 SO Q&A“如何使用 BETWEEN 子句创建 CoreData SUBQUERY?”以及对此聪明​​的引用标题为“SUBQUERY 没那么可怕”作者:@MaciekCzarnik。

我经历了减少必要迭代的过程,直到我可以逐行比较 SUBQUERY 语法。虽然它实际上并没有解决我的问题,但这确实鼓励我尝试多种谓词语法替代方案,直到我返回对 SUBQUERY 的理解并能够确认原始语法是正确的。它为我提供了我的大脑可以理解和处理的示例类型,以加深对 SUBQUERY 实际工作原理的理解。

因为您当前没有更好的内容可阅读...

var iterationOne: [Meal] {
    let meals: [Meal] = []//all meals
    var results = [Meal]()
    for meal in meals {
        var portionsMatchingQuery = Set<Portion>()
        if let mealPortionsToCheck = meal.mealsPortions {
            for case let portion as Portion in mealPortionsToCheck {
                if portions.contains(portion) == true {
                    portionsMatchingQuery.insert(portion)
                }
            }
            if portionsMatchingQuery.count > 0 { results.append(meal) }
        }
    }
    return results
}

可以简化为 _

var iterationTwo: [Meal] {
    let meals: [Meal] = []//all meals
    let results = meals.filter { meal in
        var portionsMatchingQuery = Set<Portion>()
        if let mealPortionsToCheck = meal.mealsPortions {
            for case let portion as Portion in mealPortionsToCheck {
                if portions.contains(portion) == true {
                    portionsMatchingQuery.insert(portion)
                }
            }
            return portionsMatchingQuery.count > 0
        }
        return false
    }
    return results
}

可以简化为 _

var iterationThree: [Meal] {
    let meals: [Meal] = []//all meals
    let results = meals.filter { meal in
        let portionsMatchingQuery = meal.mealsPortions?.filter { portion in
            for case let portion as Portion in meal.mealsPortions! {
                return portions.contains(portion) == true
            }
            return false
        }
        return portionsMatchingQuery?.count ?? 0 > 0
    }
    return results
}

可以简化为 _

var iterationFour: [Meal] {
    let meals: [Meal] = []//all meals
    let results = meals.filter { meal in
        meal.mealsPortions?.filter { portion in                
            for case let portion as Portion in meal.mealsPortions {
                return portions.contains(portion) == true
            }
            return false
        }.count ?? 0 > 0
    }
    return results
}

iterationFour == predicatePartB

Short answer...

let portions = food.foodPortions
let predicatePartB = NSPredicate(format: "(SUBQUERY(mealsPortions, $p, $p IN %@).@count != 0)", portions!)

OR, if I prepare a computed property for portions...

var portions: NSSet {
    if let p = food.foodPortions { return p }
    return NSSet()
}

then in the creation of the predicate I'm not required to force unwrap the optional NSSet...

let predicatePartB = NSPredicate(format: "(SUBQUERY(mealsPortions, $p, $p IN %@).@count != 0)", portions)

Most people reading this probably won't want to know the detail but nonetheless I feel compelled to write this down, so the long answer is...

... in two parts, or at least recognises two contributors who helped me solve it.

Part 1

Primarily @JoakimDanielson for confirming that SUBQUERY was the right path to a solution and for taking the time to work out the syntax for my case and also for questioning what eventually turned out to be a very basic error, the problem was not my SUBQUERY syntax but in fact the manner in which I was preparing the NSSet that I used in the predicate string.

All I needed to do was change...

let portions = NSSet(object: food.foodPortions as Any)

to...

let portions = food.foodPortions

after which I could either force unwrap it in the creation of the predicate, or otherwise prepare a computed property (the solution I chose) - as detailed above in the short answer.

This was simply an error as a result of my inadequate understanding of the collections NSSet and Set. A refresher of the swift.org docs helped me.

Part 2

Secondly this SO Q&A "How to create a CoreData SUBQUERY with BETWEEN clause?" and the reference to this clever article titled "SUBQUERY Is Not That Scary" by @MaciekCzarnik.

I went through the process of reducing the necessary iteration until I could line for line compare the SUBQUERY syntax. While it didn't actually solve my problem, this did encourage me to try numerous predicate syntax alternatives until I returned with an understanding of SUBQUERY and was able to confirm the original syntax was correct. It provided me with the type of example my brain can comprehend and work through to develop an understanding of how SUBQUERY actually works.

Because you have nothing better to read at the current moment in time...

var iterationOne: [Meal] {
    let meals: [Meal] = []//all meals
    var results = [Meal]()
    for meal in meals {
        var portionsMatchingQuery = Set<Portion>()
        if let mealPortionsToCheck = meal.mealsPortions {
            for case let portion as Portion in mealPortionsToCheck {
                if portions.contains(portion) == true {
                    portionsMatchingQuery.insert(portion)
                }
            }
            if portionsMatchingQuery.count > 0 { results.append(meal) }
        }
    }
    return results
}

can be simplified to _

var iterationTwo: [Meal] {
    let meals: [Meal] = []//all meals
    let results = meals.filter { meal in
        var portionsMatchingQuery = Set<Portion>()
        if let mealPortionsToCheck = meal.mealsPortions {
            for case let portion as Portion in mealPortionsToCheck {
                if portions.contains(portion) == true {
                    portionsMatchingQuery.insert(portion)
                }
            }
            return portionsMatchingQuery.count > 0
        }
        return false
    }
    return results
}

can be simplified to _

var iterationThree: [Meal] {
    let meals: [Meal] = []//all meals
    let results = meals.filter { meal in
        let portionsMatchingQuery = meal.mealsPortions?.filter { portion in
            for case let portion as Portion in meal.mealsPortions! {
                return portions.contains(portion) == true
            }
            return false
        }
        return portionsMatchingQuery?.count ?? 0 > 0
    }
    return results
}

can be simplified to _

var iterationFour: [Meal] {
    let meals: [Meal] = []//all meals
    let results = meals.filter { meal in
        meal.mealsPortions?.filter { portion in                
            for case let portion as Portion in meal.mealsPortions {
                return portions.contains(portion) == true
            }
            return false
        }.count ?? 0 > 0
    }
    return results
}

iterationFour == predicatePartB

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文