为什么我不能在光滑的查询中使用选项

时间:2014-07-01 16:10:46

标签: scala playframework slick

为了节省我必须创建这么多方法,我尝试将Option's传递给我的方法,然后检查是否定义了Option,如果是,则应用过滤器。

def getUsers(locationId: Option[Int], companyId: Int, salary: Option[Int]): List[User] = {
  val query = for {
    u <- users if u.companyId === companyId && (locationId.isDefined && u.locationId === locationId.get) && (salary.isDefined && u.salary >= salary.get)

  }
  query.list()
}

我收到错误说:

polymorphic expression cannot be instantiated to expected type;

IntelliJ errors are expected Boolean actual Column[Boolean].

这种类型的条款在光滑的查询中是不可能的,或者我只是做错了吗?

5 个答案:

答案 0 :(得分:6)

我无法告诉你为什么,但这会为我编译:

def getUsers(locationId: Option[Int], companyId: Int, salary: Option[Int]): List[User] = {
  val query = for {
    u <- users if u.companyId === companyId && locationId.isDefined && u.locationId === locationId.get && salary.isDefined && u.salary >= salary.get
  } yield(u)
  query.list()
}

请注意,没有括号,您必须yield,否则query的返回类型将为Unit

答案 1 :(得分:3)

当然,不要在这里看到任何问题,只需使用filter(或withFilter)并映射选项。

def getUsers(locationId: Option[Int], companyId: Int, salary: Option[Int]): List[User] = (for {
  u <- users filter(u=>
    if (u.companyId === companyId.bind) && 
    (locationId.map(_.bind === u.locationId).getOrElse(true)) && 
    (salary.map(_.bind <= u.salary).getOrElse(true))
  )
} yield u).list()

使用过滤器可以下拉到Scala以获取地图或真正的后备表达式。如果您从u < users if...开始,那么就无法使用Scala条件。 bind调用只是逃避潜在的恶意输入(即,如果params来自应用程序之外)。

答案 2 :(得分:1)

为什么它不起作用

正如cvot注意到in his comment,这不起作用的原因是:

  

Slick将None转换为SQL NULL,包括SQLs 3值逻辑NULL传播,所以(None === a)是无,无论a的值是什么...基本上如果表达式中有任何东西是无,整个表达式将为None,因此过滤器表达式将被视为false,查询结果将为空。

也就是说,有一种方法可以获得所需的行为(仅在提供可选值时进行过滤)。

达到理想行为的方法

需要注意的关键是,Scala将汇总归结为map / flatMap / withFilter / filter次调用的组合。如果我理解正确的话,那么当它将Scala理解编译成SQL查询时,它将与结果结构一起使用。

这允许我们在部分中构建查询:

val baseQuery = for {
  u <- users if u.companyId === companyId
} yield u

val possiblyFilteredByLocation = if (locationId.isDefined) {
  baseQuery.withFilter(u => u.locationId === locationId.get
} else baseQuery

val possiblyFilteredBySalaryAndOrLocation = if (salary.isDefined) {
  possiblyFilteredByLocation.withFilter(u => u.salary >= salary.get)
} else possiblyFilteredByLocation

possiblyFilteredBySalaryAndOrLocation.list()

我们可以使用varfold

来简化此操作
var query = for {
  u <- users if u.companyId === companyId
} yield u
query = locationId.fold(query)(id => query.withFilter(u => u.locationId === id))
query = salary.fold(query)(salary => query.withFilter(u => u.salary >= salary))
query.list()

如果我们经常这样做,我们可以将Option上的这种过滤模式概括为:

// Untested, probably does not compile
implicit class ConditionalFilter(query: Query) {
  def ifPresent[T](value: Option[T], predicate: (Query, T) => Query) = {
    value.fold(query)(predicate(query, _))
  }
}

然后我们可以将整个过滤器链简化为:

query
  .ifPresent[Int](locationId, (q, id) => q.withFilter(u => u.locationId === id))
  .ifPresent[Int](salary, (q, s) => q.withFilter(u => u.salary >= s))
  .list()

答案 3 :(得分:1)

您可以使用以下解决方案(对于Slick 3.3.x):

def getUsers(locationId: Option[Int], companyId: Int, minSalary: Option[Int]) = 
  users.
    .filter(_.company === companyId)
    .filterOpt(locationId)(_.locationId === _)
    .filterOpt(minSalary)(_.salary >= _)

答案 4 :(得分:-1)

因为Slick查询被转换为SQL,它没有isDefined的概念和Option类的get方法。 但是你可以通过调用查询外部的方法并传递结果(通过选项上的map函数)来解决这个问题。

以下代码应该修复它:

def getUsers(locationId: Option[Int], companyId: Int, salary: Option[Int]): List[User] = {
    val locationAndSalary = for {
        locId <- locationId;
        sal <- salary
    } yield (locId, sal)

    locationAndSalary.map(locAndSal => {
        val query = for {
            u <- users if u.companyId === companyId && u.locationId === locAndSal._1 && u.salary >= locAndSal._2)  
        } yield u
        query.list()
    }).getOrElse(List[User]()) //If the locationID or salary is None, return empty list.
}

locationAndSalary可能看起来很奇怪,但我们只是在locationIdsalary都有值并将结果存储在元组中时使用for comprehension才能使用值locationId在第一个位置,工资在第二个位置。以下链接对其进行了解释:Scala: for comprehensions with Options

编辑:根据@Ende Neu的回答,如果添加yield语句,代码会编译,但我仍然认为我的解决方案更多的是&#34; Scala方式&#34;。