我有一个有趣的用例。基本上我需要合并sourceUser: UserRow
和targetUser: UserRow
。合并意味着将sourceUser
标记为非活动状态,并将所有源链接帐户(详细信息)复制到目标用户。所有遗漏的细节,例如可以检出UserRow
光滑生成的类型等in this github project。
我可以在SQL中做到这一点没有问题,例如。
UPDATE "user" SET active=false WHERE user_id=${sourceUserId};
INSERT INTO linked_account(user_id, provider_key, provider_hashed)
SELECT ${targetUserId}, provider_key, provider_hashed
FROM linked_account
WHERE user_id=${sourceUserId}
我的Slick尝试是通用dao和光滑动作的混合,无法看到如何以事务方式撰写。我也找不到同时选择和插入的方法,即这种尝试不能编译:
def merge(targetUser: UserRow, sourceUser: UserRow) : Future[Unit] = {
// deactivate the source user
update(sourceUser.copy(active = false))
val action = (for {
// get the linked accounts to source
linkedAccountSource <- LinkedAccount
// match the source user
source <- User if source.id === sourceUser.id && linkedAccountSource.userId === source.id
// insert into target the previously matched source linked
// account but having target user as user_id
// ........................................ doesn't compile here VVVVVVVVVVVVVVVVVVVVVVV
linkedAccountTarget <- (LinkedAccount += linkedAccountSource.copy(userId = targetUser.id))
// match the target user attempting to update set active=false
target <- User if target.id === targetUser.id
} yield linkedAccountTarget).transactionally
db.run(action)
}
更新我做到了这一点,但我收到了最后一次编译错误,基本上是想将targetUser.id
分配给sourceUser.id
def merge(targetUser: UserRow, sourceUser: UserRow) : Future[Unit] = {
// define an update DBIOAction to deactivate sourceUser
val updateAction = User.filter(_.id === sourceUser.id).update(sourceUser.copy(active = false))
// selects all linkedAccount from sourceUser but yield the userId of the targetUser
val selectAction = (for {
linkedAccount <- LinkedAccount
user <- User if user.id === sourceUser.id && user.id === linkedAccount.userId
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
// define an insert DBIOAction to insert all the selected linked accounts from sourceUser to targetUser
val insertAction = selectAction.flatMap(LinkedAccount ++= _)
// combine both actions using >> and transactionally
db.run((updateAction >> insertAction).transactionally).map(_ => ())
}
和最后一个错误,我尝试在不同的地方做.as[LinkedAccountRow]
,但它不喜欢它:
[error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/UserDao.scala:101: type mismatch;
[error] found : Seq[(Long, String, String, Option[java.sql.Timestamp])]
[error] required: Iterable[generated.Tables.LinkedAccountRow]
[error] val insertAction = selectAction.flatMap(LinkedAccount ++= _)
[error] ^
答案 0 :(得分:4)
如果你改变了这一点,这一切都不会起作用:
...
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
...
以这些方式:
...
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
.map(LinkedAcount.tuppled)
...
此外,您可以使用巧妙的技巧在一个INSERT...SELECT
操作中执行SQL
。这将模仿您想要的SQL:
INSERT INTO linked_account(user_id, provider_key, provider_hashed)
SELECT ${targetUserId}, provider_key, provider_hashed
FROM linked_account
WHERE user_id=${sourceUserId}
这将是以下几点:
LinkedAccount
.map(acc => (acc.userId, acc.prividerKey, providerHashed))
.forceInsertQuery(
for {
acc <- LinkedAccount if acc.userId === sourceUser.id
} yield (acc))
.map(acc =>
(acc.userId, acc.privderKey, acc.providerHashed))
)
这forceInsertQuery
显然是关键所在。您基本上可以在其中包含任何内容(例如查询多个连接等),只要它最终生成与原始map
(forceInsertQuery
之前的那个)匹配的投影。
附加说明:
正如forceInsertQuery
内的内容实际上是常规Query
,您可以执行以下操作:预编译。这将是这样的:
// this would need to be obviously created once, perhaps in your DAO
// as member value
private val query = Compiled(sourceUserId: Rep[Long] => (for {
acc <- LinkedAccount if acc.userId === sourceUserId
} yield (acc))
.map(acc =>
(acc.userId, acc.privderKey, acc.providerHashed)
)
)
...
// and inside your method
LinkedAccount
.map(acc => (acc.userId, acc.prividerKey, providerHashed))
.forceInsertQuery( query(sourceUser.id) )
答案 1 :(得分:1)
我现在正在打电话,但我认为你应该在列元组而不是<>
上使用as[LinkedAccountRow]
运算符。
<>
将两个参数作为在列元组和目标对象之间转换的函数对。对于案例类,这是apply.tupled
,unapply
函数。
最合适的地方应该是yield
理解的selectAction
条款。