diesel: inner_join 转发查询

diesel: inner_join forward query

我正在学习使用inner_join进行正向查询


pub async fn hello(pool: web::Data<DbPool>) -> HttpResult {
    let conn = pool.get().map_err(ErrorInternalServerError)?;

    let data: Vec<(models::Book, models::User)> = books::table
        .inner_join(users::table)
        .load::<(models::Book, models::User)>(&conn)
        .map_err(ErrorInternalServerError)?;

    Ok(HttpResponse::Ok().json(data))
}

我得到了正确的数据

[
    [{
        "id": 1,
        "name": "js",
        "user_id": 1
    }, {
        "id": 1,
        "username": "admin"
    }],
    [{
        "id": 2,
        "name": "rust",
        "user_id": 1
    }, {
        "id": 1,
        "username": "admin"
    }]
]

但是这个json数据的结构不是我想要的

我想要这个嵌套结构

[{
    "id": 1,
    "name": "js",
    "user": {
        "id": 1,
        "username": "admin"
    }
}, {
    "id": 2,
    "name": "rust",
    "user": {
        "id": 1,
        "username": "admin"
    }
}]

我不知道如何转换,有没有最佳实践


更多信息

架构:

table! {
    books (id) {
        id -> Unsigned<Bigint>,
        name -> Varchar,
        user_id -> Unsigned<Bigint>,
    }
}

table! {
    users (id) {
        id -> Unsigned<Bigint>,
        username -> Varchar,
    }
}

joinable!(books -> users (user_id));

allow_tables_to_appear_in_same_query!(
    books,
    users,
);

型号:

#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, Debug, Clone)]
#[belongs_to(User, foreign_key = "user_id")]
#[table_name = "books"]
pub struct Book {
    #[serde(skip_deserializing)]
    pub id: PK,
    pub name: String,
    pub user_id: PK,
}

#[derive(Identifiable, Queryable, Serialize, Deserialize, Debug, Clone)]
#[table_name = "users"]
pub struct User {
    pub id: PK,
    pub username: String,
}

柴油版

diesel = { version = "1.4.4", features = ["mysql", "r2d2", "chrono", "numeric"] }

你真正的问题是 serde crate

如果您有这样的数据类型 (String, i32, ...) serde 将在输出中创建数组,请参见以下代码

fn main() {
   let val = ("Hello", 123, 2.5);

   let result = serde_json::to_string(&val).unwrap();
   println!("{}", result); // ["Hello",123,2.5] 
}

所以如果你想解决它,你可以这样做

首先制作您的自定义模型

struct User {
   id:      i32,
   username: String,
}

struct ResponseModel {
   id:   i32,
   name: String,
   user: User,
} 

现在为 ResponseModel

实施 From 特征
impl From<(models::Book, models::User)> for ResponseModel {
    fn from(values: (models::Book, models::User)) -> Self {
        Self {
            id: values.0.id,
            name: values.0.name,
            user: User {
                id: values.1.id,
                username: values.1.username,
            },
        }
    }
}

现在像这样更改 hello fn

pub async fn hello(pool: web::Data<DbPool>) -> HttpResult {
   let conn = pool.get().map_err(ErrorInternalServerError)?;

   let data: Vec<ResponseModel> = books::table
       .inner_join(users::table)
       .load::<(models::Book, models::User)>(&conn)
       .map(|x| x.into_iter().map(ResponseModel::from).collect())
       .map_err(ErrorInternalServerError)?;

   Ok(HttpResponse::Ok().json(data))
}

一个简单的方法是使用 #[serde(flatten)]

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BookWithUser<B, U> {
    #[serde(flatten)]
    book: B,
    user: U,
}

impl<B, U> From<(B, U)> for BookWithUser<B, U> {
    fn from((b, u): (B, U)) -> Self {
        Self { book: b, user: u }
    }
}

#[derive(Identifiable, Associations, Queryable, Serialize, Deserialize, Debug, Clone)]
#[belongs_to(models::user::User, foreign_key = "user_id")]
#[table_name = "books"]
pub struct Book {
     ...
}

然后像这样使用

#[get("/with_user")]
pub async fn list_with_user(pool: web::Data<DbPool>) -> HttpResult {
    use schema::*;
    let res = web::block(move || -> Result<_, DbError> {
        let conn = pool.get()?;
        let res = books::table
            .inner_join(users::table)
            .load::<(mods::book::Book, mods::user::User)>(&conn)
            .map(|x| x.into_iter().map(mods::book::BookWithUser::from))?
            .collect::<Vec<_>>();
        Ok(res)
    })
    .await?
    .map_err(ErrorInternalServerError)?;

    Ok(HttpResponse::Ok().json(res))
}