根据表达式在 fetchrequest 中创建列

Posted

技术标签:

【中文标题】根据表达式在 fetchrequest 中创建列【英文标题】:Create column in fetchrequest based on expression 【发布时间】:2017-03-16 09:51:10 【问题描述】:

摘要

我希望能够创建一个基于来自核心数据实体的 fetchRequest 中的表达式的新值。

拥有具有此属性的实体(短版)

  Name              Attributes
[Course] -> licenseStart, name, id

我想在该实体上创建一个 fetchResquest 并检索一个字典,其中包含所有属性以及一个基于此表达式的更多属性:

licenseStart.doubleValue / 1000 < NSDate().timeIntervalSince1970 ? "Actually" : "Cooming Soon"

所以结果会是:

[ "licenseStart":1,"id":3, "name":"A", "status":0, ...
  "licenseStart":2,"id":4, "name":"B", "status":0, ...
  "licenseStart":3,"id":6, "name":"B", "status":0, ...
  "licenseStart":4,"id":2, "name":"C", "status":0, ...
  "licenseStart":5,"id":1, "name":"D", "status":1, ...
  "licenseStart":6,"id":7, "name":"E", "status":1, ...
]

发布

我想针对几天以来一直面临的问题提出一个问题。我尝试过的每种方法最终都会得到更复杂的解决方案。

让我给你解释一下情况:

我有一个具有不同属性的实体的 CoreData 模型,其中一个是 license start 这是一个 unix 时间戳编号,用于确定课程何时开始。

所以,当我想填充数据以创建带有课程的 UITableView 时,我从数据库中毫无问题地获取所有这些信息,我使用此 licenseStart 属性来计算基于的部分在公式上,这是 UITableView 的代码

UITableViewController

let entity = NSEntityDescription.entityForName("Course", inManagedObjectContext: managedObjectContext!)
        let req = NSFetchRequest()
        let predicate = sectionProtocol?.FRCPredicate
        let sort = [NSSortDescriptor(key: "licenseStart", ascending: true), NSSortDescriptor(key: "name", ascending: true), NSSortDescriptor(key: "courseId", ascending: true)]
        req.entity = entity
        req.sortDescriptors = sort
        req.predicate = predicate
        /* NSFetchedResultsController initialization
        a 'nil' 'sectionNameKeyPath' generates a single section */
        var aFetchedResultsController = NSFetchedResultsController(fetchRequest: req, managedObjectContext: managedObjectContext!, sectionNameKeyPath:"dateStatus", cacheName: nil)

在这之后我的结果(小例子)是这样的

[ "licenseStart":1,"id":3, "name":"A",...
  "licenseStart":2,"id":4, "name":"B",...
  "licenseStart":3,"id":6, "name":"B",...
  "licenseStart":4,"id":2, "name":"C",...
  "licenseStart":5,"id":1, "name":"D",...
  "licenseStart":6,"id":7, "name":"E",
]

我将在 UITableView 中拥有的是:

[Actually] 
  [A]
  [B]
  [B]
  [C]
[Cooming Soon]
  [D]
  [E]

所以,我现在要做的是使用 licenseStart 创建两个部分,如何?很简单,只需使用我的模型的一个名为 dateStatus 的属性并创建两个可能使用许可开始的结果如下:

模型类

var dateStatus : Int 
        get 
            return licenseStart.doubleValue / 1000 < NSDate().timeIntervalSince1970 ? "Actually" : "Cooming Soon"
        
 

所以在我的 UITableView 中,我再次使用 dataSource 委托方法来填充行和部分。

问题

到目前为止,一切都很完美。但是现在我想添加一个排序选项,让用户对结果进行升序或降序排序,问题是当我排序时,即使用不同的排序选项升序:

let entity = NSEntityDescription.entityForName("Course", inManagedObjectContext: managedObjectContext!)
        let req = NSFetchRequest()
        let predicate = sectionProtocol?.FRCPredicate
        let sort = [NSSortDescriptor(key: "licenseStart", ascending: true), NSSortDescriptor(key: "name", ascending: false), NSSortDescriptor(key: "courseId", ascending: false)]
        req.entity = entity
        req.sortDescriptors = sort
        req.predicate = predicate
        /* NSFetchedResultsController initialization
        a 'nil' 'sectionNameKeyPath' generates a single section */
        var aFetchedResultsController = NSFetchedResultsController(fetchRequest: req, managedObjectContext: managedObjectContext!, sectionNameKeyPath:"dateStatus", cacheName: nil)

由于我基于 licenseStart 创建部分的方式,此提取的结果将与以前完全相同:

[ "licenseStart":1,"id":3, "name":"A",...
  "licenseStart":2,"id":4, "name":"B",...
  "licenseStart":3,"id":6, "name":"B",...
  "licenseStart":4,"id":2, "name":"C",...
  "licenseStart":5,"id":1, "name":"D",...
  "licenseStart":6,"id":7, "name":"E",
]

我将在 UITableView 中拥有的是:

[Actually] 
  [A]
  [B]
  [B]
  [C]
[Coming Soon]
  [D]
  [E]

为什么?因为 licenseStart 都是不同的,它使用 NSSortDescriptors 中的第一个元素来检索数据。这不是我想要的行为,我想要的是让这些部分的顺序与以前完全相同,但里面的元素排列不同

即:

[Actually] 
  [C]
  [B]
  [B]
  [A]
[Coming Soon]
  [E]
  [D]

可能的解决方案

我的想法是在 fetchrequest 期间创建一个新列,如果我能够在检索数据时评估此表达式 licenseStart.doubleValue / 1000 &lt; NSDate().timeIntervalSince1970 ? 0 : 1,我将在结果中拥有这个带有 0 和 1 的新列:

[ "licenseStart":1,"id":3, "name":"A", "status":0, ...
  "licenseStart":2,"id":4, "name":"B", "status":0, ...
  "licenseStart":3,"id":6, "name":"B", "status":0, ...
  "licenseStart":4,"id":2, "name":"C", "status":0, ...
  "licenseStart":5,"id":1, "name":"D", "status":1, ...
  "licenseStart":6,"id":7, "name":"E", "status":1, ...
]

现在我可以使用这个 status 在 NSSortDescriptors 中创建部分,[NSSortDescriptor(key: "status", ascending: true),NSSortDescriptor(key: "licenseStart", ascending: true), NSSortDescriptor(key: "name", ascending: true), NSSortDescriptor(key: "courseId", ascending: true)] 用于升序,[NSSortDescriptor(key: "status", ascending: true),NSSortDescriptor(key: "licenseStart", ascending: false), NSSortDescriptor(key: "name", ascending: false), NSSortDescriptor(key: "courseId", ascending: false)] 用于降序。

这会给我我想要的行为.. 但我还没有找到一种好的和干净的方法来做到这一点。我尝试使用 NSExpressions 来执行此操作,但我不能将 小于 与 CoreData fetch 一起使用..

另外,一个可能的解决方案是在我的数据模型中创建这个新列并在保存数据时生成这个值,但这不是一种有效的方法,因为每次用户填充时,这个 licenseStart 值都必须与当前时间戳进行比较这张表。

我在问是否有人对此问题有一个简短的想法或聪明的解决方案。

mysql 或 SQLite 中,基于某个值或表达式创建新列很容易,但在 coredata 中,我是新手,所以我没有看到明确的方法。

非常感谢。

【问题讨论】:

这是一个很长的问题。您能否在某处(可能在顶部)放置一个摘要,显示您想要实现的最终结果以及您实际得到的结果。这似乎有点像意识流 - 有助于让你的想法下降,但很难看出实际问题是什么。 添加了总结,感谢@AshleyMills 的反馈:) 【参考方案1】:

我有一种情况,我想将一些元素划分为列表顶部,而不是基于 core-data 属性。我使用 fetchedResultsController 进行了获取,然后进行了一次运行以将元素放入两个数组 (O(n)) 中,保持按原始排序进行排序。当元素改变时,我必须在我的数组中找到它们来改变它们,但这并没有我想象的那么难。

更简单的解决方案可能是使用两个 fetchedResultsController。

【讨论】:

【参考方案2】:

尽量不要将 CoreData 视为常规的关系 sql 数据库,您可以在其中在查询中创建列。

似乎您需要的只是为您的实体添加另一个属性status。每次更新 licenseStart 时,只需设置 status 值即可。

如果您使用的是 codegen,那么

extension Course 
    func updateLicenseStart(date: Date) 
        licenseStart = date
        status = licenseStart.doubleValue / 1000 < Date().timeIntervalSince1970 ? 0 : 1
     

或重命名基础属性...

extension Course 
    var licenseStart 
        set 
            _licenseStart = newValue 
            status = licenseStart.doubleValue / 1000 < Date().timeIntervalSince1970 ? 0 : 1
        
        get  return _licenseStart 
     

如果您要维护自己的模型类,则不能将属性观察器添加到 @NSManaged var,但可以删除 @NSManaged 并覆盖 setget...

public var licenseStart: Date 
    set 
        willChangeValueForKey("licenseStart")
        setPrimitiveValue(newValue, forKey: "licenseStart")
        status = licenseStart.doubleValue / 1000 < Date().timeIntervalSince1970 ? 0 : 1
        didChangeValueForKey("licenseStart")
    
    get 
        willAccessValueForKey("licenseStart")
        let date = primitiveValueForKey("licenseStart") as? Date
        didAccessValueForKey("licenseStart")
        return date
    

【讨论】:

是的,我第一次想到了那个解决方案。但问题是,当您从服务器检索数据并将其保存在 Core Data 中时,状态将基于那一刻,但我需要的是每次用户填充 tableview 时基于当前时间戳的状态和我将 fetchResultsController.fetchedObjects 传递给 tableview 委托方法。 啊,我想我明白了。我们在谈论多少行?时间需要有多准确?您能否在应用程序启动/前台/在填充表格并在每一行上设置状态之前进行批量更新? 这不是数量的问题,而是应用程序具有所有这些离线完整功能,让用户甚至可以离线完成课程(它下载课程的所有内容)然后当连接再次激活它会启动更新功能。所以也许几天会有不一致的部分(当它是当前时,它可能会说即将推出):S。我能够创建一个带有 propertiesToFetch 的 NSExpression 并创建一个新值(但我不能使用像上面 licenseStart.doubleValue / 1000 &lt; NSDate().timeIntervalSince1970 ? 0 : 1 这样的复杂函数

以上是关于根据表达式在 fetchrequest 中创建列的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 .txt 文件中的关键字在 Python 中创建列表?

根据列表值在 Power Query 中创建条件列

如何根据用户输入使用新谓词重新运行@FetchRequest?

在 R 中创建表达式树

在 LINQ 中创建表达式

在 groupby 之后在 pandas DataFrame 的列表变量中创建一个列表