1"use strict";(self.webpackChunksea_ql_blog=self.webpackChunksea_ql_blog||[]).push([[9687],{626:e=>{e.exports=JSON.parse('{"permalink":"/blog/2025-10-20-sea-orm-2.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2025-10-20-sea-orm-2.0.md","source":"@site/blog/2025-10-20-sea-orm-2.0.md","title":"SeaORM 2.0: New Entity Format","description":"SeaORM\u2019s current entity format is explicit, but it can feel verbose, making it difficult to write by hand. In SeaORM 2.0, we\u2019re introducing a more information\u2011dense entity format, along with new features that make relational queries easier and more powerful.","date":"2025-10-20T00:00:00.000Z","tags":[{"inline":true,"label":"news","permalink":"/blog/tags/news"}],"readingTime":13.22,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/blog/img/SeaQL.png","key":null,"page":null}],"frontMatter":{"slug":"2025-10-20-sea-orm-2.0","title":"SeaORM 2.0: New Entity Format","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/blog/img/SeaQL.png","image":"https://www.sea-ql.org/blog/img/SeaORM%202.0%20Banner.png","tags":["news"]},"unlisted":false,"prevItem":{"title":"SeaORM 2.0: Entity First Workflow","permalink":"/blog/2025-10-30-sea-orm-2.0"},"nextItem":{"title":"Seaography 2.0: A Powerful and Extensible GraphQL Framework \ud83e\udded","permalink":"/blog/2025-10-08-seaography"}}')},2004:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>a,toc:()=>d});var a=n(626),s=n(4848),i=n(8453);const r={slug:"2025-10-20-sea-orm-2.0",title:"SeaORM 2.0: New Entity Format",author:"SeaQL Team",author_title:"Chris Tsang",author_url:"https://github.com/SeaQL",author_image_url:"https://www.sea-ql.org/blog/img/SeaQL.png",image:"https://www.sea-ql.org/blog/img/SeaORM%202.0%20Banner.png",tags:["news"]},o=void 0,l={authorsImageUrls:[void 0]},d=[{value:"Gist",id:"gist",level:2},{value:"The Schema",id:"the-schema",level:2},{value:"Under the hood",id:"under-the-hood",level:2},{value:"A bit of history",id:"a-bit-of-history",level:3},{value:"Generating the <code>Relation</code> enum and <code>Related</code> impl",id:"generating-the-relation-enum-and-related-impl",level:3},{value:"Generating the <code>Model</code>",id:"generating-the-model",level:3},{value:"More relation types",id:"more-relation-types",level:2},{value:"Many to many relation with junction table",id:"many-to-many-relation-with-junction-table",level:3},{value:"Self-referencing relations",id:"self-referencing-relations",level:3},{value:"Composite foreign key",id:"composite-foreign-key",level:3},{value:"New find/filter by unique key methods",id:"new-findfilter-by-unique-key-methods",level:2},{value:"(Smart) Entity Loader",id:"smart-entity-loader",level:2},{value:"Backwards compatibility",id:"backwards-compatibility",level:2},{value:"Updated codegen",id:"updated-codegen",level:2},{value:"More to come",id:"more-to-come",level:2},{value:"SQL Server Support",id:"sql-server-support",level:2},{value:"\ud83d\udda5\ufe0f SeaORM Pro: Admin Panel",id:"\ufe0f-seaorm-pro-admin-panel",level:2},{value:"\ud83c\udf1f Sponsors",id:"-sponsors",level:2},{value:"Gold Sponsor",id:"gold-sponsor",level:4},{value:"GitHub Sponsors",id:"github-sponsors",level:4},{value:"\ud83e\udd80 Rustacean Sticker Pack",id:"-rustacean-sticker-pack",level:2}];function c(e){const t={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",h4:"h4",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("img",{alt:"SeaORM 2.0 Banner",src:"/blog/img/SeaORM%202.0%20Banner.png"}),"\n",(0,s.jsx)(t.p,{children:"SeaORM\u2019s current entity format is explicit, but it can feel verbose, making it difficult to write by hand. In SeaORM 2.0, we\u2019re introducing a more information\u2011dense entity format, along with new features that make relational queries easier and more powerful."}),"\n",(0,s.jsx)(t.h2,{id:"gist",children:"Gist"}),"\n",(0,s.jsx)(t.p,{children:"Suppose now we're making a blogging platform. We're designing a simple schema starting from users."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:"user 1-1 profile\nuser 1-N post\npost 1-N comment\n"})}),"\n",(0,s.jsxs)(t.p,{children:["Defining the ",(0,s.jsx)(t.code,{children:"user"})," Entity as follows:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",metastring:'title="user.rs"',children:'use sea_orm::entity::prelude::*;\n\n#[sea_orm::model]\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "user")]\npub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n pub name: String,\n #[sea_orm(unique)]\n pub email: String,\n #[sea_orm(has_one)]\n pub profile: HasOne<super::profile::Entity>,\n #[sea_orm(has_many)]\n pub posts: HasMany<super::post::Entity>,\n}\n\nimpl ActiveModelBehavior for ActiveModel {}\n'})}),"\n",(0,s.jsx)(t.p,{children:"You will be able to query a user with their profile along with all their blog posts in one operation:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'// join paths:\n// user -> profile\n// user -> post -> comment\nlet user: Option<user::ModelEx> = user::Entity::load()\n .filter_by_id(12)\n .with(profile::Entity)\n .with((post::Entity, comment::Entity))\n .one(db)\n .await?;\n\n// has the following shape:\nassert_eq!(user.unwrap(), user::ModelEx {\n id: 12,\n name: "Bob".into(),\n email: "[email protected]".into(),\n profile: HasOne::Loaded(profile::ModelEx {\n ..\n }),\n posts: HasMany::Loaded(vec![\n post::ModelEx {\n title: "Nice day for a walk",\n comments: vec![comment::ModelEx { .. }, .. ],\n },\n post::ModelEx { .. },\n ]),\n});\n'})}),"\n",(0,s.jsxs)(t.p,{children:["Full example can be found ",(0,s.jsx)(t.a,{href:"https://github.com/SeaQL/sea-orm/blob/master/examples/quickstart/src/main.rs",children:"here"}),"."]}),"\n",(0,s.jsx)(t.h2,{id:"the-schema",children:"The Schema"}),"\n",(0,s.jsxs)(t.p,{children:["The definition of the ",(0,s.jsx)(t.code,{children:"user"})," Entity is already shown above, let's look at ",(0,s.jsx)(t.code,{children:"profile"})," and ",(0,s.jsx)(t.code,{children:"post"}),":"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",metastring:'title="profile.rs"',children:'use sea_orm::entity::prelude::*;\n\n#[sea_orm::model]\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "profile")]\npub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n pub picture: String,\n #[sea_orm(unique)]\n pub user_id: i32,\n #[sea_orm(belongs_to, from = "user_id", to = "id")]\n pub user: HasOne<super::user::Entity>,\n}\n\nimpl ActiveModelBehavior for ActiveModel {}\n'})}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"profile"})," has a foreign key ",(0,s.jsx)(t.code,{children:"user_id"})," -> ",(0,s.jsx)(t.code,{children:"user.id"})," relating back to the ",(0,s.jsx)(t.code,{children:"user"})," table. By applying a ",(0,s.jsx)(t.code,{children:"unique"})," constraint, we're effectively making the relation 1-1."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",metastring:'title="post.rs"',children:'use sea_orm::entity::prelude::*;\n\n#[sea_orm::model]\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "post")]\npub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n pub user_id: i32,\n pub title: String,\n pub body: String,\n #[sea_orm(belongs_to, from = "user_id", to = "id")]\n pub author: HasOne<super::user::Entity>,\n #[sea_orm(has_many)]\n pub comments: HasMany<super::comment::Entity>,\n}\n\nimpl ActiveModelBehavior for ActiveModel {}\n'})}),"\n",(0,s.jsxs)(t.p,{children:["Similarly, ",(0,s.jsx)(t.code,{children:"post"})," has a foreign key to the ",(0,s.jsx)(t.code,{children:"user"})," table, but without the unique constraint. The ",(0,s.jsx)(t.code,{children:"comment"})," entity is very similar."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",metastring:'title="comment.rs"',children:'use sea_orm::entity::prelude::*;\n\n#[sea_orm::model]\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "comment")]\npub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n pub comment: String,\n pub post_id: i32,\n #[sea_orm(belongs_to, from = "post_id", to = "id")]\n pub post: HasOne<super::post::Entity>,\n}\n\nimpl ActiveModelBehavior for ActiveModel {}\n'})}),"\n",(0,s.jsx)(t.h2,{id:"under-the-hood",children:"Under the hood"}),"\n",(0,s.jsx)(t.p,{children:"It looks clean right? But what magic is going on under the hood? Let's break it down step by step, at the end it actally expands into the current entity format. Such that this new entity format is perfectly backwards-compatible."}),"\n",(0,s.jsx)(t.h3,{id:"a-bit-of-history",children:"A bit of history"}),"\n",(0,s.jsx)(t.p,{children:"In the early days of SeaORM, language servers didn't have very strong macro expansion capabilities. As such, types generated by derive macros can't be seen and picked up by IDEs for auto-completion. That's why in that era crates would prefer to be macro-light, it's also the reason SeaORM (still) has an expanded entity format. The current compact entity format expands into the exp
1anded entity format."}),"\n",(0,s.jsxs)(t.h3,{id:"generating-the-relation-enum-and-related-impl",children:["Generating the ",(0,s.jsx)(t.code,{children:"Relation"})," enum and ",(0,s.jsx)(t.code,{children:"Related"})," impl"]}),"\n",(0,s.jsxs)(t.p,{children:["An existing SeaORM entity has three sections, ",(0,s.jsx)(t.code,{children:"Model"}),", ",(0,s.jsx)(t.code,{children:"Relation"})," and ",(0,s.jsx)(t.code,{children:"ActiveModel"}),"."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:"use sea_orm::entity::prelude::*;\n\npub struct Model { .. }\n\npub enum Relation { .. }\n\nimpl Related<> for Entity {}\n\nimpl ActiveModelBehavior for ActiveModel {}\n"})}),"\n",(0,s.jsx)(t.p,{children:"We've hid the Relation / Related section into the Model itself."}),"\n",(0,s.jsxs)(t.p,{children:["For the ",(0,s.jsx)(t.code,{children:"user"})," entity, a ",(0,s.jsx)(t.code,{children:"Relation"})," enum is generated:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'pub enum Relation {\n #[sea_orm(has_one = "super::profile::Entity")]\n Profile,\n #[sea_orm(has_many = "super::post::Entity")]\n Post,\n}\n'})}),"\n",(0,s.jsxs)(t.p,{children:["In addition, some ",(0,s.jsx)(t.code,{children:"Related"})," impls are also generated:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:"impl Related<super::profile::Entity> for Entity {\n fn to() -> RelationDef {\n Relation::Profile.def()\n }\n}\n\nimpl Related<super::post::Entity> for Entity {\n fn to() -> RelationDef {\n Relation::Post.def()\n }\n}\n"})}),"\n",(0,s.jsxs)(t.p,{children:["For the ",(0,s.jsx)(t.code,{children:"profile"})," entity, it gets slightly more elaborate:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'pub enum Relation {\n #[sea_orm(\n belongs_to = "super::user::Entity",\n from = "Column::UserId",\n to = "super::user::Column::Id"\n )]\n User,\n}\n\nimpl Related<super::user::Entity> for Entity {\n fn to() -> RelationDef {\n Relation::User.def()\n }\n}\n'})}),"\n",(0,s.jsxs)(t.h3,{id:"generating-the-model",children:["Generating the ",(0,s.jsx)(t.code,{children:"Model"})]}),"\n",(0,s.jsx)(t.p,{children:"Note that the Model is now not 'plain-old', as it has some nested fields:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:"pub struct Model {\n pub id: i32,\n pub name: String,\n pub email: String,\n pub profile: HasOne<super::profile::Entity>, // <-\n pub posts: HasMany<super::post::Entity>, // <-\n}\n"})}),"\n",(0,s.jsx)(t.p,{children:"We have to drop these fields to make the struct simple, such that we can still do the following:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'let user = user::Model { id: 1, name: "Bob".into(), email: "[email protected]" };\n'})}),"\n",(0,s.jsx)(t.p,{children:"That's why as you may have noticed, we need an attribute macro:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:"#[sea_orm::model]\n"})}),"\n",(0,s.jsxs)(t.p,{children:["We call the compound Model with nested fields ",(0,s.jsx)(t.code,{children:"ModelEx"}),". May be it can have a better name, but you should rarely need to name this type explicitly."]}),"\n",(0,s.jsx)(t.h2,{id:"more-relation-types",children:"More relation types"}),"\n",(0,s.jsx)(t.p,{children:"What makes SeaORM stand-apart is that we support many different kinds of relations in real-world complex schemas. Many users may never encounter them, but they're there when you need them."}),"\n",(0,s.jsx)(t.h3,{id:"many-to-many-relation-with-junction-table",children:"Many to many relation with junction table"}),"\n",(0,s.jsx)(t.p,{children:"SeaORM is unique in its ability to model many\u2011to\u2011many relations as first\u2011class constructs. Most APIs treat both 1-N and M-N synonymously, and you never need to manually specify the junction table when writing queries."}),"\n",(0,s.jsxs)(t.p,{children:["Let's say ",(0,s.jsx)(t.code,{children:"post"})," M-N ",(0,s.jsx)(t.code,{children:"tag"}),", all you need is to specify the junction with the ",(0,s.jsx)(t.code,{children:"via"})," attribute:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'mod post {\n use sea_orm::entity::prelude::*;\n\n #[sea_orm::model]\n #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n #[sea_orm(table_name = "post")]\n pub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n #[sea_orm(has_many, via = "post_tag")]\n pub tags: HasMany<super::tag::Entity>,\n }\n\n impl ActiveModelBehavior for ActiveModel {}\n}\n'})}),"\n",(0,s.jsx)(t.p,{children:"This is the junction table:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'mod post_tag {\n use sea_orm::entity::prelude::*;\n\n #[sea_orm::model]\n #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]\n #[sea_orm(table_name = "post_tag")]\n pub struct Model {\n #[sea_orm(primary_key, auto_increment = false)]\n pub post_id: i32,\n #[sea_orm(primary_key, auto_increment = false)]\n pub tag_id: i32,\n #[sea_orm(belongs_to, from = "post_id", to = "id")]\n pub post: Option<super::post::Entity>,\n #[sea_orm(belongs_to, from = "tag_id", to = "id")]\n pub tag: Option<super::tag::Entity>,\n }\n\n impl ActiveModelBehavior for ActiveModel {}\n}\n'})}),"\n",(0,s.jsx)(t.p,{children:"To query posts with tags, you call the exact same method, without even mentioning the junction."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:"let posts = post::Entity::load().with(tag::Entity).all(db).await?;\n\nassert_eq!(posts, vec![post::ModelEx {\n tags: vec![ tag::ModelEx { .. }, .. ]\n}]);\n\n// or you can use the model loader API:\n\nlet posts: Vec<post::Model> = post::Entity::find().all(db).await?;\nlet tags: Vec<tag::Model> = posts.load_many(tag::Entity, db).await?;\n"})}),"\n",(0,s.jsx)(t.h3,{id:"self-referencing-relations",children:"Self-referencing relations"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'#[sea_orm::model]\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]\n#[sea_orm(table_name = "staff")]\npub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n pub name: String,\n pub manager_id: i32,\n #[sea_orm(\n self_ref,\n relation_enum = "Manager",\n from = "manager_id",\n to = "id"\n )]\n pub manager: HasOne<Entity>,\n}\n'})}),"\n",(0,s.jsx)(t.p,{children:"The generated enum will have a variant like:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'pub enum Relation {\n #[sea_orm(\n belongs_to = "Entity",\n from = "Column::ManagerId",\n to = "Column::Id",\n )]\n Manager, // <- relation_enum\n}\n'})}),"\n",(0,s.jsxs)(t.p,{children:["This aspect is not so different from 1.0. A ",(0,s.jsx)(t.code,{children:"Related"})," impl will not be generated, but the relation can still be used in loader or join queries."]}),"\n",(0,s.jsx)(t.h3,{id:"composite-foreign-key",children:"Composite foreign key"}),"\n",(0,s.jsx)(t.p,{children:"You don't use it very often, but SeaORM actually supports it since 0.1. Some lines are omitted for brevity."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'mod composite_a {\n #[sea_orm::model]\n #[sea_orm(table_name = "composite_a")]\n pub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n #[sea_orm(unique_key = "pair")] // <- name this unique key\n pub left_id: i32,\n #[sea_orm(unique_key = "pair")]\n pub r
1ight_id: i32,\n #[sea_orm(has_one)]\n pub b: Option<super::composite_b::Entity>,\n }\n}\n\nmod composite_b {\n #[sea_orm::model]\n #[sea_orm(table_name = "composite_b")]\n pub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n pub left_id: i32,\n pub right_id: i32,\n #[sea_orm(\n belongs_to,\n from = "(left_id, right_id)",\n to = "(left_id, right_id)"\n )]\n pub a: Option<super::composite_a::Entity>,\n }\n}\n'})}),"\n",(0,s.jsxs)(t.p,{children:["Designing an ORM that can support all these scenario is ",(0,s.jsx)(t.em,{children:"hard"})," with exploding complexities, but this is exactly what makes SeaORM powerful and sets it apart. Even if you don\u2019t need these features today, choosing SeaORM helps ensure your application is future-proof."]}),"\n",(0,s.jsx)(t.h2,{id:"new-findfilter-by-unique-key-methods",children:"New find/filter by unique key methods"}),"\n",(0,s.jsx)(t.p,{children:"To improve ergonomics, SeaORM 2.0 now automatically generates type-safe shorthand methods for unique keys:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'user::Entity::find_by_email("[email protected]").one(db).await?\n\nuser::Entity::load().filter_by_email("[email protected]").one(db).await?\n'})}),"\n",(0,s.jsx)(t.p,{children:"It even works on composite unique keys!"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:"// the name `pair` is user-defined above\ncomposite_a::Entity::find_by_pair((1, 2)).one(db).await?\n\ncomposite_a::Entity::load().filter_by_pair((2, 3)).one(db).await?\n"})}),"\n",(0,s.jsx)(t.h2,{id:"smart-entity-loader",children:"(Smart) Entity Loader"}),"\n",(0,s.jsxs)(t.p,{children:["We've spent a lot of engineering effort in designing the new Entity Loader. You can see it as magic, because it eliminates the N+1 problem even when doing nested queries while preventing over-fetching ",(0,s.jsx)(t.em,{children:"at the same time"}),"."]}),"\n",(0,s.jsx)(t.p,{children:"In the nested query we shown you in the beginning, 3 queries are executed:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-sql",children:"SELECT FROM user JOIN profile WHERE id = $\nSELECT FROM post JOIN user WHERE user_id IN (..)\nSELECT FROM comment WHERE post_id IN (..)\n"})}),"\n",(0,s.jsx)(t.p,{children:"For 1-1 relations, it does a join and select up to three tables together in a single query."}),"\n",(0,s.jsx)(t.p,{children:"For 1-N or M-N relations, it uses the data loader. Note that, it's a single query even for M-N relation, as the junction table will be joined."}),"\n",(0,s.jsx)(t.p,{children:"For nested queries, it uses the data loader. It consolidates the id of all the posts in the 2nd query and issue one query for the comments."}),"\n",(0,s.jsx)(t.h2,{id:"backwards-compatibility",children:"Backwards compatibility"}),"\n",(0,s.jsx)(t.p,{children:"The new Entity format is perfectly backwards compatible as it gets expanded into the current compact format. However, the Entity Loader generates a bit of extra code under the hood, and it's not available for compact entities. We've introduced a transitional macro, in case you want to take advantage of the Entity Loader without migrating to the new format."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:'#[sea_orm::compact_model] // <- add this\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "post")]\npub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n pub user_id: i32,\n pub body: String,\n // #[sea_orm(belongs_to, from = "user_id", to = "id")] // <- not needed\n pub author: HasOne<super::user::Entity>, // <- add these compound fields\n}\n\n// the rest of the Entity file is exactly the same as before\n\n#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]\npub enum Relation { .. }\n'})}),"\n",(0,s.jsx)(t.p,{children:"Now you can do:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-rust",children:"post::Entity::load().with(super::user::Entity)..\n"})}),"\n",(0,s.jsx)(t.p,{children:"The current compact entity format has more flexibility, as you can:"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["add new ",(0,s.jsx)(t.code,{children:"Relation"})," enum variants"]}),"\n",(0,s.jsxs)(t.li,{children:["add ",(0,s.jsx)(t.code,{children:"on_condition"})," on relations"]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"updated-codegen",children:"Updated codegen"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"sea-orm-cli"})," has been updated to generate this new entity format, including with Seaography support. You can run the same entity generate command, but with an additional ",(0,s.jsx)(t.code,{children:"--entity-format dense"})," flag. We may turn this on by default in the future."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-sh",children:"sea-orm-cli generate entity --output-dir ./src/entity --entity-format dense\n"})}),"\n",(0,s.jsx)(t.h2,{id:"more-to-come",children:"More to come"}),"\n",(0,s.jsx)(t.p,{children:"SeaORM 2.0 is shaping up to be our most significant release yet - with a few breaking changes, plenty of enhancements, and a clear focus on developer experience. We'll dive into Entity-first workflow in the next post, so keep an eye out for the next update!"}),"\n",(0,s.jsxs)(t.p,{children:["SeaORM 2.0 will launch alongside SeaQuery 1.0. If you make extensive use of SeaQuery, we recommend checking out our earlier blog post on ",(0,s.jsx)(t.a,{href:"https://www.sea-ql.org/blog/2025-08-30-sea-query-1.0/",children:"SeaQuery 1.0"})," to get familiar with the changes."]}),"\n",(0,s.jsxs)(t.p,{children:["SeaORM 2.0 has reached its release candidate phase. We'd love for you to try it out and help shape the final release by ",(0,s.jsx)(t.a,{href:"https://github.com/SeaQL/sea-orm/discussions/2548",children:"sharing your feedback"}),"."]}),"\n",(0,s.jsx)(t.h2,{id:"sql-server-support",children:"SQL Server Support"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.a,{href:"https://www.sea-ql.org/SeaORM-X/",children:"SQL Server for SeaORM"})," offers the same SeaORM API for MSSQL. We ported all test cases and examples, complemented by MSSQL specific documentation. If you are building enterprise softw
1are, you can ",(0,s.jsx)(t.a,{href:"https://forms.office.com/r/1MuRPJmYBR",children:"request commercial access"}),". It is currently based on SeaORM 1.0, but we will offer free upgrade to existing users when SeaORM 2.0 is finalized."]}),"\n",(0,s.jsx)(t.h2,{id:"\ufe0f-seaorm-pro-admin-panel",children:"\ud83d\udda5\ufe0f SeaORM Pro: Admin Panel"}),"\n",(0,s.jsx)("img",{src:"/blog/img/sea-orm-pro-light.png#light"}),"\n",(0,s.jsx)("img",{src:"/blog/img/sea-orm-pro-dark.png#dark"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.a,{href:"https://github.com/SeaQL/sea-orm-pro",children:"SeaORM Pro"})," is an admin panel solution allowing you to quickly and easily launch an admin panel for your application - frontend development skills not required, but certainly nice to have!"]}),"\n",(0,s.jsx)(t.p,{children:"SeaORM Pro has been updated to support the latest features in SeaORM 2.0."}),"\n",(0,s.jsx)(t.p,{children:"Features:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"Full CRUD"}),"\n",(0,s.jsx)(t.li,{children:"Built on React + GraphQL"}),"\n",(0,s.jsx)(t.li,{children:"Built-in GraphQL resolver"}),"\n",(0,s.jsx)(t.li,{children:"Customize the UI with TOML config"}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.a,{href:"https://www.sea-ql.org/blog/2025-09-30-sea-orm-rbac/",children:"Role Based Access Control"})," ",(0,s.jsx)(t.em,{children:"(new in 2.0)"})]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"-sponsors",children:"\ud83c\udf1f Sponsors"}),"\n",(0,s.jsx)(t.h4,{id:"gold-sponsor",children:"Gold Sponsor"}),"\n",(0,s.jsx)("a",{href:"https://qdx.co/",children:(0,s.jsx)("img",{src:"https://www.sea-ql.org/static/sponsors/QDX.svg",width:"128"})}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.a,{href:"https://qdx.co/",children:"QDX"})," pioneers quantum dynamics-powered drug discovery, leveraging AI and supercomputing to accelerate molecular modeling.\nWe're grateful to QDX for sponsoring the development of SeaORM, the SQL toolkit that powers their data intensive applications."]}),"\n",(0,s.jsx)(t.h4,{id:"github-sponsors",children:"GitHub Sponsors"}),"\n",(0,s.jsx)(t.p,{children:"If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization."}),"\n",(0,s.jsxs)(t.p,{children:["A big shout out to our ",(0,s.jsx)(t.a,{href:"https://github.com/sponsors/SeaQL",children:"GitHub sponsors"}),":"]}),"\n",(0,s.jsxs)("div",{class:"row margin-left--sm",children:[(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/ryanswrt",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/87781?u=10a9d256e741f905f3dd2cf641de8b325720732e&v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/OteroRafael",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/175388115?v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/higumachan",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/1011298?u=de4c2f0d0929c2c6dc433981912f794d0e50f2cd&v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/wh7f",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/59872041?v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/marcson909",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/16665353?v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/numeusxyz",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/82152211?v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/data-intuitive",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/15045722?v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/caido-community",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/168573261?v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--md",href:"https://github.com/marcusbuffett",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/1834328?u=fd066d99cf4a6333bfb3927d1c756af4bb8baf7e&v=4"})})})]}),"\n",(0,s.jsx)("br",{}),"\n",(0,s.jsxs)("div",{class:"row margin-left--sm",children:[(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/yuly3",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/25814001?u=4b57756e7d8060e48262a9edba687927fe7934a6&v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/kallydev",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/36319157?u=5be882aa4dbe7eea97b1a80a6473857369146df6&v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/manfredcml",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/27536502?u=b71636bdabbc698458b32e2ac05c5771ad41097e&v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/tugascript",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/64930104?u=ad9f63e8e221dbe71bf23de59e3611c99cda1181&v=4"})})}),(0,s.jsx)("div",{class:"avatar",children:(0,s.jsx)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/deansheather",children:(0,s.jsx)("img",{src:"https://avatars.githubusercontent.com/u/11241812?u=260538c7d8b8c3c5350dba175ebb8294358441e0&v=4"})})})]}),"\n",(0,s.jsx)(t.h2,{id:"-rustacean-sticker-pack",children:"\ud83e\udd80 Rustacean Sticker Pack"}),"\n",(0,s.jsx)(t.p,{children:"The Rustacean Sticker Pack is the perfect way to express your passion for Rust.\nOur stickers are made with a premium water-resistant vinyl with a unique matte finish."}),"\n",(0,s.jsx)(t.p,{children:"Sticker Pack Contents:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography"}),"\n",(0,s.jsx)(t.li,{children:"Mascots: Ferris the Crab x 3, Terres the Hermit Crab"}),"\n",(0,s.jsx)(t.li,{children:"The Rustacean wordmark"}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.a,{href:"https://www.sea-ql.org/sticker-pack/",children:"Support SeaQL and get a Sticker Pack!"})}),"\n",(0,s.jsx)("a",{href:"https://www.sea-ql.org/sticker-pack/",children:(0,s.jsx)("img",{style:{borderRadius:"25px"},alt:"Rustacean Sticker Pack by SeaQL",src:"https://www.sea-ql.org/static/sticker-pack-1s.jpg"})})]})}function h(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>r,x:()=>o});var a=n(6540);const s={},i=a.createContext(s);function r(e){const t=a.useContext(i);return a.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),a.createElement(i.Provider,{value:t},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.