【问题标题】:Eager loading of relations急切加载关系
【发布时间】:2021-04-28 09:32:42
【问题描述】:

我有三个具有这些关系的模型:

艺术家 1n 歌曲 1n 播放

我想加载 Plays 并急切地加载它的歌曲和艺术家。

我在 PHP 框架 Eloquent 中轻松做到了这一点。而且它只需要 3 个数据库请求(每个模型一个)。

这在柴油上怎么可能?

我认为 Play 模型应该如下所示:

#[derive(Queryable, Serialize, Deserialize)]
pub struct Play {
    pub id: u64,
    pub song_id: u64,
    pub song: Song, // <-- ????
    pub date: NaiveDateTime,
    pub station_id: u64,
}

并加载类似这样的内容:

// loads one model only at the moment
// it should load the related models Song and Artist too
let items = plays.filter(date.between(date_from, date_to)).load(&*conn)?;

生成的结构应为 JSON 序列化以供 REST API 使用。但是我找不到以一种简单有效的方式获取所需结构的方法。

【问题讨论】:

    标签: rust orm eager-loading rust-diesel


    【解决方案1】:

    首先:如果您在网上发布有关柴油的问题,请始终包含架构的相关部分,否则其他人需要猜测它的外观。 我将为您的问题假设以下架构:

    table! {
        plays (id)
           id -> BigInt,
           song_id -> BigInt,
           date -> Timestamp,
           station_id -> BigInt,
        }
    
    }
    
    table! {
        songs(id) {
            id -> BigInt,
            name -> Text,
        }
    }
    
    joinable!(plays -> songs (song_id));
    

    现在您的 Play 结构存在一些问题。 u64 不是柴油机支持的类型。查看this 文档,了解哪些类型与BigInt SQL 类型兼容。 这意味着我将假设以下结构实现Queryable

    #[derive(Queryable, Serialize, Deserialize)]
    pub struct Play {
        pub id: i64,
    //    pub song_id: u64, leave this out as it is a duplicate with `song` below
        pub song: Song,
        pub date: NaiveDateTime,
        pub station_id: i64,
    }
    
    #[derive(Queryable, Serialize, Deserialize)]
    pub struct Song {
        pub id: i64,
        pub name: String,
    }
    

    现在来回答您的核心问题:起初,柴油机没有像其他 ORM 中已知的单一模型的概念。如果您曾经使用过这样的东西,请不要尝试将这些知识应用于柴油,它不会起作用。有一组特征描述每个结构的能力。当我们在这里谈论查询时,我将专注于QueryableQueryable 旨在将查询结果(可能包括零个、一个或多个表)映射到 rust 数据结构。默认情况下(通过使用derive)它通过映射字段​​结构来做到这一点,这意味着您的选择子句需要匹配派生Queryable的结构。 现在回到你的具体问题。这意味着我们必须构造一个返回(i64, (i64, String), NaiveDateTime, i64) 的查询(或者更好的是具有相应SQL 类型的字段)。 对于这种情况,这就像使用单个连接一样简单:

    plays::table.inner_join(songs::table)
       .select((plays::id, songs::all_columns, plays::date, plays::station_id))
    
    

    对于映射来说,这里唯一重要的是 select 子句。您可以附加/预先添加您喜欢的任何其他 QueryDsl 方法。

    【讨论】:

    • 所以我的模型带有外国 ID 并查询 Vec&lt;(Play, Song, Artist)&gt;。然后我将map() 的结果传递给另一个包含参考模型而不是 ID 的模型:FullPlay{..., song: FullSong, ...}。对吗?
    • 我现在就是这样做的。给我所需的结果,而且比预期的要快。
    【解决方案2】:

    实现此目的的一种方法是连接另外两个表。不幸的是,我没有弄清楚如何避免枚举所有表字段。这不是一个优雅的解决方案,但它应该可以工作。

    fn select_plays_with_song_and_artist(
        conn: &Conn,
        date_from: NaiveDate,
        date_to: NaiveDate,
    ) -> Result<Vec<(models::Play, models::Song, models::Artist)>> {
        plays::table                
        .filter(plays::date.between(date_from, date_to))               
        .join(songs::table.join(artists::table))                                             
        .select((                                                               
            (
                plays::id,  
                plays::song_id,     
                plays::date,     
                plays::station_id,                                      
            ),
            (
                songs::id,
                // other relevant fields...
            ),
            (
                artist::id,
                // other relevant fields...
            ),
         ))                                                                           
         .load::<(models::Play, models::Song, models::Artist)>(conn)
    }
    

    【讨论】:

    • 我在柴油文档中找到了类似的解决方案。所有这些的问题是,我必须在第二步中构建我的结构。我需要它作为 REST API。但似乎没有办法只运行一次。
    • 这通常可以通过将所有内容放入一个结构中来解决,然后在结构的某些部分使用#[serde(flatten)]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-19
    • 2016-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-01
    相关资源
    最近更新 更多