PetaPoco支持將結果集中的一行映射到到兩個以及更多POCO,但是如何處理一對多和多對多關系?
1、PetaPoco 支持將結果映射為多個POCO類型,提供了另一種方法來處理SQL的Join查詢。
背景
多POCO查詢背后的想法是生成一個SQL JOIN查詢,並將每個表返回的列自動的映射給POCO表示。換句話說,不是一行被映射為一個POCO,前N列被映射到一個POCO,后N列的映射給另一個,等等..
示例:
var sql = PetaPoco.Sql.Builder
.Append("SELECT articles.*, authors.*")
.Append("FROM articles")
.Append("LEFT JOIN users ON articles.user_id = users.user_id");
var result = db.Query<article, user, article>( (a,u)=>{a.user=u; return a }, sql);
注意事項:
- SQL查詢的結果返回自兩個表中.
- 前兩個泛型參數類型指定了查詢時每行數據中包含數據的POCO類型.
- 第三泛型參數是返回集類型- 一般與第一個表類型相同,但也可能為其他類型.
- 查詢方法將它的第一個參數作為一個回調委托,可用來連接兩個對象的關系.
在這個例子中,我們返回了一個IEnumerable<article> ,其中每個article對象都有一個通過user屬性對與之相關user的引用.
選擇分割點
支持多POCO查詢的本質就是確定返回的結果集應如何分割。如:那些列映射到那些POCO上。返回列必須和在Query<>中傳遞的泛型參數順序相同。如:前N列映射給T1,后N列映射給T2.等等....從返回結果的最左邊的列開始將其映射給第一個POCO。搜索一個分割點並順序的將后續的列映射給下一個POCO類型。
如果一個列名已經映射到當前的POCO類型,就認為它是一個分割點。思考這個序列:
article_id, title, content, user_id, user_id, name
//POCOs:
class article
{
long article_id { get; set; }
string title { get; set; }
string content { get; set; }
long user_id { get; set; }
}
class user
{
long user_id { get; set; }
string name { get; set; }
}
//Query
db.Query<article, user, article>( ... )
這里我們關注的是use_id,在映射結果集時,第一個user_id列將被映射給article Poco。當看到第二個user_id列時,PetaPoco會意識到它已經映射給了articles 屬性並開始將之映射給user Poco。分割點最后的處理方法是確定當前POCO類型中不存在某一列但存下一個POCO中存在。注意如果一個列不在當前的POCO類型中也不在下一個POCO中,那么它將被忽略。
自動連接 POCO's
PetaPoco 也可以猜出屬性關系並返回的對象的引用賦值給它。
比如:
var result = db.Query<article, user, article>( (a,u)=>{a.user=u; return a }, sql);
可以簡單的寫成:
var result = db.Query<article, user>(sql);
這里有兩點需要注意:
- 不需要指定返回類型參數(第三個參數).返回集將永遠是類型
T1. - 用於設置對象關系的回調函數也不需要.
很明顯,這個是通過PetaPoco來猜測工作的,且在通常大多數情況 是值得的。要使其起作用,T2到T5必須有一個與他左側類型相同的屬性類型。換句話說:
T1必須具有T2的一個屬性T1或T2必須具有T3的一個屬性T1或T2或T3 必須具備類型T4的一個屬性- 等等...
再者,屬性是從右至左進行查找的,所以如果T2和T3都具備類型T4的一個屬性, T3的屬性將被使用.
2、一對多和多對一
實例標識和廢棄的POCO
"Instance Identity"的准確含義是什么? 我的意思是如果一條特定的記錄在兩個或者更多的地方在所有的情況下從查詢返回相同的POCO實例,或者POCO實例唯一標識該記錄。例如,假設你正在做一個文章表和作者表的Join查詢,如果兩篇文章,作者是相同的,那么都將引用同一作者的對象實例。
PetaPoco的多POCO查詢一直為每一行數據創建一個POCO實例.所以在上面的例子中,每行都將創建一個新的作者對象.為了正確的得到實例標識,我們將最終丟棄重復值。所以不要把一對多和多對一映射當成是一個改善效率的方法,只在更多准確的對象映射對你有用是使用。
責任方式回調
在創建行個人POCOs所謂的“責任方式回調”,其工作是連接對象為該行成一個對象圖。這個最簡單的方法是簡單地分配在LHS對象的一個屬性的RHS對象。這是什么PetaPoco的自動映射。這是一個簡單而快速的方法,但它並沒有提供我們談論的對象標識。
自動映射和簡單關系
在我們涉及責任方式回調前,讓我們看看一個簡單的自動映射多POCO查詢:
var posts = db.Fetch<post, author>(@"
SELECT * FROM posts
LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id
");
使用自動映射,第一個泛型參數為返回類型.所以這個例子將返回一個List<post>,所以只要post對象有author對象的一個屬性,PetaPoco將進行連接並創建author對象.
責任方式回調:
var posts = db.Fetch<post, author, post>(
(p,a)=> { p.author_obj = a; return p; },
@"SELECT * FROM posts
LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id
");
注意以下兩點:
這里有一個額外的泛型參數組-<post, author, post>.最后一個參數指示了返回集合元素的類型。使用一個自定義的relator,你也許決定使用不同的類來描述連接行。
lambda函數用來連接post和author.
- 多對一關系
為了實現多對一關系,所有我們需要做的就是保持一個映射對應的對象並重復使用.(多對一,因為很多行可能同時對應於一個對象,只需將對應的對象保存下來,並每次重復使用之生成映射關系)
//用於保存為一的authors
var authors = new Dictionary<long, author>(); var posts = db.Fetch<post, author, post>( (p, a) => { //獲取存在的author對象 author aExisting;
//如果author已經存在 if (authors.TryGetValue(a.id, out aExisting)) a = aExisting; else authors.Add(a.id, a); //連接對象 p.author_obj = a; return p; }, "SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id" );
如果需要在多個地方使用,可將之封裝起來:
class PostAuthorRelator
{
//已知作者的字典集合
Dictionary<long, author> authors = new Dictionary<long, author>();
public post MapIt(post p, author a)
{
// 嘗試獲取已知的對象, 如果不存在則保存當前
author aExisting;
if (authors.TryGetValue(a.id, out aExisting))
a = aExisting;
else
authors.Add(a.id, a);
// 連接對象
p.author_obj = a;
return p;
}
}
//使用方法
var posts = db.Fetch<post, author, post>(
new PostAuthorRelator().MapIt,
"SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id"
);
- 一對多關系
在一對多關系中,我們想要從右側集合中將每個左側對象組合為一個對象的集合。翻開我們上面的例子,我們想要一個作者的列表,每個作者都有一個該作者文章的集合:
SELECT * FROM authors LEFT JOIN posts ON posts.author = authors.id ORDER BY posts.id
左邊的作者需要合並成一個POCO, 每個作者右邊的文章需要整合成一個列表.
為了支持這個,Petapoco允許一個責任回調返回Null來表示沒有為當前記錄提供,為了去掉最終的記錄,
class AuthorPostRelator
{
public author current;
public author MapIt(author a, post p)
{
// 終止調用.因為我們可以從本函數返回null
//我們需要為后續使用null回調做好准備
// parameters
if (a == null)
return current;
// 是否當前的作者與我們正在處理的相同
if (current != null && current.id == a.id)
{
// Yes,
current.posts.Add(p);
//返回null表示我們已經處理完當前作者
return null;
}
// 這是作者與當前處理的不同,或者是第一次處理,之前還未處理過
// 保存當前作者
var prev = current;
// 設置新的當前作者
current = a;
current.posts = new List<post>();
current.posts.Add(p);
// Return the now populated previous author (or null if first time through)
return prev;
}
}
var authors = db.Fetch<author, post, author>(
new AuthorPostRelator().MapIt,
"SELECT * FROM authors LEFT JOIN posts ON posts.author = authors.id ORDER BY posts.id"
);
So let's look at a one-to-many relator:
