Haskell 持久库 - 如何将数据从我的数据库获取到我的前端?
Haskell Persistent Library - How do I get data from my database to my frontend?
您好,感谢您抽出宝贵时间。
我正在尝试创建一个具有递增计数器按钮的网站。我希望当前计数器保持不变,如果有人访问我的页面,则应显示当前计数器。
每次单击按钮增加计数器时,都应发送一个请求。该请求不包含有关计数器值的任何信息。服务器——在我的例子中是一个 warp web 服务器——应该更新数据库中的计数器值,读取更新后的值,如果成功则将其发送到前端,如果失败则发送一条错误消息。
到目前为止,只有更新有效,因为我没有设法弄清楚如何从数据库中获取数据到前端。
这是我的存储库模块中应该进行更新的代码:
{-# LANGUAGE EmptyDataDecls, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving#-}
{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings, QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell, TypeFamilies, DataKinds, FlexibleInstances#-}
{-# LANGUAGE DerivingStrategies, StandaloneDeriving, UndecidableInstances #-}
module Repository (increaseCounter) where
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (runStderrLoggingT)
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Control.Monad.Reader
import Data.Text
import Data.Maybe
-- setting up the Counter entity with a unique key so I can use the getBy function
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Counter
counterName String
counterCount Int Maybe
UniqueCounterName counterName
deriving Show
|]
increaseCounter :: IO ()
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
updateWhere [CounterCounterName ==. "unique name"] [CounterCounterCount +=. Just 1]
counterEntity <- getBy $ UniqueCounterName name
liftIO $ print counterEntity
这会编译并实际保留计数器并在每次调用时更新值。但是从类型可以看出,更新后它只将计数器值打印到控制台。
我似乎无法理解如何使用从 getBy 函数 return 编辑的数据。
文档说:
getBy :: (PersistUniqueRead backend, MonadIO m, PersistRecordBackend record backend) =>
Unique record -> ReaderT backend m (Maybe (Entity record))
'backend m' 基本上是一个嵌套的 monad 吗?
假设我只想发送计数器的值,如果它是 Just Int
,如果它是 Nothing
,我想发送 return -1。
我假设我无法修改 increaseCounter 函数,使其类型为 Maybe Int
。但是我如何将函数传递到 monad 中/访问其中的值以向前端发送响应?
如果这个问题太肤浅了and/or我缺乏太多的知识来继续这一点,你能推荐好的信息来源吗?像好的教程或 youtube 频道之类的东西?
谢谢!
您可以忽略 getBy
类型签名的所有单子部分。如果您让代码进行类型检查,counterEntity
的类型为 Maybe (Entity Counter)
,这就是这里的所有重要内容。
如果查询失败(即,table 中没有该计数器的记录),则 counterEntity
为 Nothing
。否则,它是 Just
一个 Entity Counter
包含检索到的记录:
case counterEntity of
Just e -> ...
这个 e :: Entity Counter
可以通过 entityVal
变成 Counter
。 Counter
的所需字段可以用 counterCounterCount
提取。结果将是 Maybe Int
,因为您将该字段标记为 Maybe
,因此您将有另一层 Maybe
来解压:
case counterEntity of
Nothing -> -1 -- no record for this counter
Just e -> case counterCounterCount (entityVal e) of
Nothing -> -1 -- record, but counter value missing
Just v -> v
您需要 return 来自 increaseCounter
的这个值,因此最终版本将如下所示:
increaseCounter :: IO Int
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
updateWhere [CounterCounterName ==. "unique name"] [CounterCounterCount +=. Just 1]
counterEntity <- getBy $ UniqueCounterName "unique name"
return $ case counterEntity of
Nothing -> -1
Just e -> case counterCounterCount . entityVal $ e of
Nothing -> -1
Just v -> v
无论您以前在何处成功使用 increaseCounter
来增加计数器,您现在都需要写入:
updatedCounterValue <- increaseCounter
你可以将普通的旧 updatedCounterValue :: Int
传递给前端。
您可能会发现使用 upsertBy
更明智,它可以在计数器记录丢失时插入它,否则更新它。它还 return 是 inserted/updated 实体,为您节省了单独的 getBy
调用。我也不明白你为什么用 Maybe
标记 counterCount
。为什么要在 table 中插入一个没有值的计数器?如果你想表示“无计数”,“0”不是更好的起始值吗?
所以,我将架构重写为:
Counter
counterName String
counterCount Int -- no Maybe
UniqueCounterName counterName
deriving Show
和 increaseCounter
函数为:
increaseCounter :: IO Int
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
let name = "unique name"
counterEntity <- upsertBy (UniqueCounterName name)
(Counter name 1)
[CounterCounterCount +=. 1]
return $ counterCounterCount (entityVal counterEntity)
插入 1 个计数或增加现有计数。
最后,作为一般的设计方法,最好将数据库迁移和连接设置移动到 main
函数中,并可能使用连接池,例如:
#!/usr/bin/env stack
-- stack --resolver lts-18.0 script
-- --package warp
-- --package persistent
-- --package persisent-sqlite
{-# LANGUAGE EmptyDataDecls, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings, QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell, TypeFamilies, DataKinds, FlexibleInstances#-}
{-# LANGUAGE DerivingStrategies, StandaloneDeriving, UndecidableInstances #-}
import Control.Monad.Logger (runStderrLoggingT)
import Database.Persist
import Database.Persist.TH
import Database.Persist.Sqlite
import Control.Monad.Reader
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp
import qualified Data.ByteString.Lazy.Char8 as C8
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Counter
counterName String
counterCount Int
UniqueCounterName counterName
deriving Show
|]
increaseCounter :: ReaderT SqlBackend IO Int
increaseCounter = do
let name = "unique name"
counterEntity <- upsertBy (UniqueCounterName name)
(Counter name 1)
[CounterCounterCount +=. 1]
return $ counterCounterCount (entityVal counterEntity)
main :: IO ()
main = runStderrLoggingT $ withSqlitePool "some_database.db" 5 $ \pool -> do
runSqlPool (runMigration migrateAll) pool
let runDB act = runSqlPool act pool
liftIO $ run 3000 $ \req res -> do
count <- runDB $ increaseCounter
res $ responseLBS
status200
[("Content-Type", "text/plain")]
(C8.pack $ show count ++ "\n")
您好,感谢您抽出宝贵时间。 我正在尝试创建一个具有递增计数器按钮的网站。我希望当前计数器保持不变,如果有人访问我的页面,则应显示当前计数器。 每次单击按钮增加计数器时,都应发送一个请求。该请求不包含有关计数器值的任何信息。服务器——在我的例子中是一个 warp web 服务器——应该更新数据库中的计数器值,读取更新后的值,如果成功则将其发送到前端,如果失败则发送一条错误消息。
到目前为止,只有更新有效,因为我没有设法弄清楚如何从数据库中获取数据到前端。 这是我的存储库模块中应该进行更新的代码:
{-# LANGUAGE EmptyDataDecls, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving#-}
{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings, QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell, TypeFamilies, DataKinds, FlexibleInstances#-}
{-# LANGUAGE DerivingStrategies, StandaloneDeriving, UndecidableInstances #-}
module Repository (increaseCounter) where
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (runStderrLoggingT)
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Control.Monad.Reader
import Data.Text
import Data.Maybe
-- setting up the Counter entity with a unique key so I can use the getBy function
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Counter
counterName String
counterCount Int Maybe
UniqueCounterName counterName
deriving Show
|]
increaseCounter :: IO ()
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
updateWhere [CounterCounterName ==. "unique name"] [CounterCounterCount +=. Just 1]
counterEntity <- getBy $ UniqueCounterName name
liftIO $ print counterEntity
这会编译并实际保留计数器并在每次调用时更新值。但是从类型可以看出,更新后它只将计数器值打印到控制台。 我似乎无法理解如何使用从 getBy 函数 return 编辑的数据。 文档说:
getBy :: (PersistUniqueRead backend, MonadIO m, PersistRecordBackend record backend) =>
Unique record -> ReaderT backend m (Maybe (Entity record))
'backend m' 基本上是一个嵌套的 monad 吗?
假设我只想发送计数器的值,如果它是 Just Int
,如果它是 Nothing
,我想发送 return -1。
我假设我无法修改 increaseCounter 函数,使其类型为 Maybe Int
。但是我如何将函数传递到 monad 中/访问其中的值以向前端发送响应?
如果这个问题太肤浅了and/or我缺乏太多的知识来继续这一点,你能推荐好的信息来源吗?像好的教程或 youtube 频道之类的东西?
谢谢!
您可以忽略 getBy
类型签名的所有单子部分。如果您让代码进行类型检查,counterEntity
的类型为 Maybe (Entity Counter)
,这就是这里的所有重要内容。
如果查询失败(即,table 中没有该计数器的记录),则 counterEntity
为 Nothing
。否则,它是 Just
一个 Entity Counter
包含检索到的记录:
case counterEntity of
Just e -> ...
这个 e :: Entity Counter
可以通过 entityVal
变成 Counter
。 Counter
的所需字段可以用 counterCounterCount
提取。结果将是 Maybe Int
,因为您将该字段标记为 Maybe
,因此您将有另一层 Maybe
来解压:
case counterEntity of
Nothing -> -1 -- no record for this counter
Just e -> case counterCounterCount (entityVal e) of
Nothing -> -1 -- record, but counter value missing
Just v -> v
您需要 return 来自 increaseCounter
的这个值,因此最终版本将如下所示:
increaseCounter :: IO Int
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
updateWhere [CounterCounterName ==. "unique name"] [CounterCounterCount +=. Just 1]
counterEntity <- getBy $ UniqueCounterName "unique name"
return $ case counterEntity of
Nothing -> -1
Just e -> case counterCounterCount . entityVal $ e of
Nothing -> -1
Just v -> v
无论您以前在何处成功使用 increaseCounter
来增加计数器,您现在都需要写入:
updatedCounterValue <- increaseCounter
你可以将普通的旧 updatedCounterValue :: Int
传递给前端。
您可能会发现使用 upsertBy
更明智,它可以在计数器记录丢失时插入它,否则更新它。它还 return 是 inserted/updated 实体,为您节省了单独的 getBy
调用。我也不明白你为什么用 Maybe
标记 counterCount
。为什么要在 table 中插入一个没有值的计数器?如果你想表示“无计数”,“0”不是更好的起始值吗?
所以,我将架构重写为:
Counter
counterName String
counterCount Int -- no Maybe
UniqueCounterName counterName
deriving Show
和 increaseCounter
函数为:
increaseCounter :: IO Int
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
let name = "unique name"
counterEntity <- upsertBy (UniqueCounterName name)
(Counter name 1)
[CounterCounterCount +=. 1]
return $ counterCounterCount (entityVal counterEntity)
插入 1 个计数或增加现有计数。
最后,作为一般的设计方法,最好将数据库迁移和连接设置移动到 main
函数中,并可能使用连接池,例如:
#!/usr/bin/env stack
-- stack --resolver lts-18.0 script
-- --package warp
-- --package persistent
-- --package persisent-sqlite
{-# LANGUAGE EmptyDataDecls, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings, QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell, TypeFamilies, DataKinds, FlexibleInstances#-}
{-# LANGUAGE DerivingStrategies, StandaloneDeriving, UndecidableInstances #-}
import Control.Monad.Logger (runStderrLoggingT)
import Database.Persist
import Database.Persist.TH
import Database.Persist.Sqlite
import Control.Monad.Reader
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp
import qualified Data.ByteString.Lazy.Char8 as C8
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Counter
counterName String
counterCount Int
UniqueCounterName counterName
deriving Show
|]
increaseCounter :: ReaderT SqlBackend IO Int
increaseCounter = do
let name = "unique name"
counterEntity <- upsertBy (UniqueCounterName name)
(Counter name 1)
[CounterCounterCount +=. 1]
return $ counterCounterCount (entityVal counterEntity)
main :: IO ()
main = runStderrLoggingT $ withSqlitePool "some_database.db" 5 $ \pool -> do
runSqlPool (runMigration migrateAll) pool
let runDB act = runSqlPool act pool
liftIO $ run 3000 $ \req res -> do
count <- runDB $ increaseCounter
res $ responseLBS
status200
[("Content-Type", "text/plain")]
(C8.pack $ show count ++ "\n")