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: