LINQ for db4o 入门






4.77/5 (22投票s)
一个演示 web 应用程序,用于展示 db4o 的 LINQ 功能。
Content
引言
新的 C# 3.0 语言中有一些非常好的新增功能。例如 自动属性、对象和集合初始化器、扩展方法、Lambda 表达式、匿名类型,当然还有 LINQ。
作为开发人员,您可能经常编写代码来访问 OLEDB、DAO、ODBC、ADO 或 ADO.NET 的数据。或者您可能正在使用对象关系映射器,如 NHibernate,甚至 ActiveRecord。您可能已经注意到,每次都需要编写比预期更多的代码。在此演示应用程序中,您将看到使用 db4o 访问数据将从未如此轻松。
Db4objects 现已将 LINQ 的数据查询能力与 db4o 的数据访问简便性结合起来。自 2008 年 2 月起,db4o 7.2 beta 版已可用(现已发布正式版)。此版本包含 db4o 的 LINQ 提供程序。
什么是 LINQ
LINQ 可能是 Visual Studio 2008 中最能激发您想象力的功能。借助 LINQ,您可以在代码中使用类似 SQL 的语法来检索各种信息。目前已有数十个 LINQ 提供程序。LINQ 提供程序是允许您查询特定类型数据的代码。Visual Studio 具有用于对象(LINQ)、SQL(DLINQ)和 XML(XLINQ)的 LINQ 提供程序。在互联网上搜索,您会找到更多提供程序,例如用于 Excel、Flickr、Google、Sharepoint、WMI 等。酷之处在于,对于每个提供程序,查询语法都是相同的。您甚至可以在一个查询中查询多个提供程序。
LINQ 查询语法看起来非常像 SQL。最大的区别在于命令顺序。您将从“from”语句开始。这样做的原因是,一旦您在 from 语句中指定了对象,intellisence 就能提供提示。
var result = from p Person where p.Age > 50 select p;
什么是 db4o
Db4o 是一个开源的原生面向对象数据库管理系统(ODBMS),适用于 .NET 和 Java 平台。作为原生 ODBMS,数据库模型和应用程序对象模型是相同的,因此,使用 db4o 持久化和查询对象无需进行映射或转换。就使用模式而言,db4o 可以作为独立数据库或网络数据库部署。最后,db4o 支持模式演进、索引、事务和并发、数据库加密以及复制服务(在 db4o 数据库和某些关系数据库之间)。目前 db4o 的最新版本是 7.2,提供两种许可:GPL 和商业运行时许可。
db4o 的 LINQ 功能
2008 年 2 月,db4objects 发布了 db4o 的 7.2 beta 版本。这是第一个包含 db4o LINQ 提供程序的版本。现在,两个月后,7.2 版本已作为“生产”版本发布,并且已推出优化后的 7.3 beta 版本。Db4o 本已相当简单,但现在有了 db4o 的 LINQ,您可以使用标准的查询语法来完成,而无需弄清楚如何使用 db4o 的原生查询语法。使用 LINQ 查询 db4o 数据库将感觉就像查询应用程序中已有的数据一样。您无需进行对象关系映射,也无需使用重量级的数据访问层。借助 db4o 的 LINQ,您只需几行代码即可完成所有操作。Linq 表达式会被解析并优化为 db4o 的快速查询接口:SODA。当找不到合适的转换时,查询将作为纯 Linq for Objects 查询执行(针对每个指定类型的对象进行测试的谓词)。
db4o(以及 db4o 的 LINQ)的基本知识
为了向您展示最基本的操作,此演示解决方案包含一个非常简单的控制台应用程序。此演示创建 2 个新对象并将它们存储在 db4o 数据库中。在每次保存操作之前和之后,都会将所有可用对象列表写入控制台。这是全部代码
using System;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
namespace ConsoleDemo
{
class Program
{
static void Main(string[] args)
{
// Uncomment the line below for starting with a fresh database.
// System.IO.File.Delete("Linq.db4o");
using (var container = Db4oFactory.OpenFile(Db4oFactory.NewConfiguration(),
"Linq.db4o"))
{
container.DumpPersons();
container.Store(new Person { FirstName = "Jan", LastName = "Jansen" });
container.DumpPersons();
container.Store(new Person { FirstName = "Piet", LastName = "Peters" });
container.DumpPersons();
}
Console.Write("\nPress any key to continue...\n");
Console.ReadKey();
}
}
public static class ExtendContainer
{
public static void DumpPersons(this IObjectContainer c)
{
Console.Write("Dump persons:\n");
var r = from Person p in c select p;
foreach (Person p in r)
{
Console.Write(string.Format("{0} {1}\n", p.FirstName, p.LastName));
// Uncomment the line below for showing the extednded object model
// Console.Write(string.Format("{0} {1} {2}\n", p.FirstName, p.LastName,
// p.Age));
}
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
// Uncomment the line below for extending your object model
// public string Age { get; set; }
}
}
此程序的输出将是
第二次运行此演示时,演示结束时数据库中将有 4 个对象,而不是 2 个。如果您在代码中声明了一个新对象并将其保存到 db4o,那么它也将是数据库中的一个新对象。如果您想多次运行此演示,只需取消注释删除数据库文件的行。
另一个很快会想到的是,如果您更改了对象会怎样。只需通过取消注释 Age
属性以及将显示该属性的控制台写入(确保文件删除语句已被注释掉)来尝试一下。如您所见,应用程序可以继续运行。旧对象将显示默认值。
关于网站演示
此演示的目的不是详尽介绍 LINQ 或 db4o 的所有功能。相反,我想向您展示在 ASP.NET 网站中它有多么容易使用。(在 Winforms 应用程序中使用它同样容易)。此演示将向您展示如何将 LINQ 用于 db4o,并结合一些流行的 .NET 数据绑定对象。每个演示都将在其自己的 .aspx 页面中显示,并且可以独立执行。
数据模型
此演示的数据模型非常简单。只有 2 个对象带有几个属性。我们有一个 Employee
对象和一个 Office
对象。这两个对象通过 Employee
对象中的 HomeBase
属性相关联,该属性的类型为 Office
。由于该属性的类型为 Office
,因此它声明了两个对象之间的关系。Employee
对象还有一个 Manager
属性,其类型为 Employee
。此属性声明了与自身相关的层级关系。
这两个对象都重写了 ToString
方法,以便于显示对象实例的含义。我只为 Office
对象提供了 Select
、Insert
、Update
和 Delete
方法。这些方法是为了让对象能够用于 DetailsView 演示
引用
如果您想使用 Linq for db4o,则至少需要添加 2 个引用。Db4objects.Db4o 是 db4o 的核心库。这个总是需要的。为了使用 Linq for db4o,您还需要 Db4objects.Db4o.Linq 库。有些 Linq 查询构造需要另外 2 个库。TreeView 演示就是这种情况。还需要使用的库是 Cecil.FlowAnalysis 和 Mono.Cecil。可惜 LINQ for db4o 仅在运行时提供关于这些库需求的警告。所有这些库都包含在 db4o v7.2 下载中。除了这些库,我还添加了 log4net 库。DataManager 使用 log4net 来创建日志文件。
管理 db4o 数据库
当然,像任何其他数据存储一样,您仍然需要指定使用哪个数据库。很快就会需要指定数据存储的使用方式。然后我们也希望在中心位置这样做。为此,我创建了静态类 db4oManager
。如果您不需要特殊配置或日志记录,则此类只需几行代码。这是此类最基本的形式。WithContainer
方法的设置方式使其易于与 Lambda 表达式一起使用。 演示将向您展示如何使用此方法。
//TODO: Use db4o's Client-Server mode
public static class db4oManager
{
const string FileName = "LINQ.db4o";
private static IConfiguration config = Db4oFactory.NewConfiguration();
public static void WithContainer(Action<iobjectcontainer /> action)
{
using (var container = Db4oFactory.OpenFile(config, FileName))
{
action(container);
}
}
}
坏消息是,当您的网站访问者超过几个人时,这将不起作用。如果您通过执行 OpenFile
方法引用容器,则在写入时文件将被锁定。这对大多数客户端应用程序来说是可以的,但对大多数 Web 应用程序来说不行。幸运的是,db4o 具有 客户端/服务器模式来解决此问题。这也可以轻松扩展到 网络客户端/服务器应用程序,其中服务器可以在不同的机器上。以下是使用 嵌入式客户端/服务器模式访问 db4o 数据库的 db4oManager
的代码。
public static class db4oManager
{
const string FileName = "LINQ.db4o";
private static IConfiguration config = Db4oFactory.NewConfiguration();
public static void WithContainer(Action<iobjectcontainer /> action)
{
IObjectServer server = Db4oFactory.OpenServer(config, FileName, 0);
try
{
using (var container = server.OpenClient())
{
action(container);
}
}
finally
{
server.Close();
}
}
}
此嵌入式客户端/服务器模式仍非最优。可以通过在应用程序级别管理服务器或甚至在应用程序级别管理只读客户端来轻松优化它。在最近(5 月 9 日)的此演示版本中,我添加了这些内容的静态属性,这些属性在静态构造函数中设置。我们现在甚至可以直接在 LINQ 查询中使用 db4oManager.Client,而无需使用 WithContainer 构造。请参阅下面的演示,了解如何使用它。这是创建我们将使用的客户端对象容器的构造函数的代码。
static db4oManager() { // Create a server instance Server = Db4oFactory.OpenServer(config, FileName, 0); log.Info("The Server instance has been created."); // Create a global client instance Client = Server.OpenClient(config); log.Info("The Global Client instance has been created."); } public static IObjectServer Server { get; private set; } public static IObjectContainer Client { get; private set; }
您必须注意,容器中的所有操作都将作为单个事务处理。因此,您只能将此 db4oManager.Client
用于只读访问。如果您想操作数据,请使用 WithContainer
构造。如果您想看到客户端/服务器配置的另一种实现,请查看 Db4oSiteCS.vsi 模板项目的一部分 httpHandler,该项目可在以下页面找到:入门套件 - 使用 db4o 的 Web 应用程序。
像大多数数据库一样,您可能也想指定索引和约束。对于大多数约束,您应该在属性的 setter 中添加验证代码。为了声明索引和唯一值约束,DataManager
类的静态构造函数是一个很好的位置。在下面的代码中,您将看到一种奇怪的字段表示法。这是因为属性使用新的 C# 3.0 语法声明,我们没有私有变量。除此之外,声明是直接的。
static db4oManager()
{
// Employee
config.ObjectClass(typeof(Employee)).ObjectField("<ID>k__BackingField"
).Indexed(true);
config.Add(new UniqueFieldValueConstraint(typeof(Employee), "<ID>k__BackingField"));
// Office
config.ObjectClass(typeof(Office)).ObjectField("<OfficeName>k__BackingField"
).Indexed(true);
config.Add(new UniqueFieldValueConstraint(typeof(Office),
"<OfficeName>k__BackingField"));
}
使用 log4net 进行日志记录
演示中的 DataManager
类还将向您展示如何响应 db4o 管理器引发的事件。讨论这些事件超出了本文的范围。如果您想了解更多信息,请查看 WithLoggingContainer
方法。您也可以在 db4o 关于事件注册表 API 的参考页中查找有关 db4o 事件的更多信息。要管理您的日志文件,没有什么比 log4net 更好的工具了。David Salter 关于 log4net 的精彩介绍可以在这里找到。
演示
所有演示都可以从 Default.aspx 页面访问。除了指向演示和各种来源的链接外,Default.aspx 页面还有 2 个按钮。一个用于删除数据库,另一个用于创建数据库并填充虚拟数据。Default.aspx 页面还将显示数据库中办公室和员工的数量。下面将介绍这些演示。
GridView 演示
您可以直接将 <asp:GridView>
绑定到 LINQ for db4o 结果,无需任何额外编码。只需像这样在您的 ASP.NET 页面上放置一个 GridView 控件
<asp:GridView runat="server" ID="GridPerson" AllowSorting="True"
DataMember="Employee" EnableSortingAndPagingCallbacks="True"
AutoGenerateColumns="False" CssClass="tableInfo" ShowHeader="False" >
<Columns>
<asp:BoundField DataField="ID" />
<asp:BoundField DataField="FirstName" />
<asp:BoundField DataField="LastName" />
<asp:BoundField DataField="Function" />
<asp:BoundField DataField="HomeBase" />
<asp:BoundField DataField="Manager" />
</Columns>
</asp:GridView>
然后,您只需要以下几行代码即可将 GridView 绑定到 db4o 数据库中的所有 Employee。
protected void Page_PreRender(object sender, EventArgs e)
{
GridPerson.DataSource = from Employee emp in db4oManager.Client select emp;
GridPerson.DataBind();
}
ListView 演示
ASP.NET 3.5 中的新控件之一,我认为它将非常受欢迎的是 <asp:ListView>
控件。ListView 控件支持 GridView 等高级控件的数据编辑、插入、删除、分页和排序语义。但是,与 GridView 不同的是,它让您完全控制生成的 html 标记。此演示不进行任何数据编辑。您可以在 DetailsView 演示中看到如何进行数据编辑。
<asp:ListView ID="ListEmployees" runat="server" ItemPlaceholderID="itemPlaceHolder">
<LayoutTemplate>
<table class="tableInfo">
<tr>
<th>ID</th>
<th>First name</th>
<th>Last name</th>
<th>Function</th>
<th>Office</th>
<th>Manager</th>
</tr>
<asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
<tr>
<td colspan="3">
<asp:DataPager ID="DataPager1" runat="server"
PagedControlID="ListEmployees" PageSize="5"
class="googleNavegationBar">
<Fields>
<pager:GooglePagerField
NextPageImageUrl="~/Images/button_arrow_right.gif"
PreviousPageImageUrl="~/Images/button_arrow_left.gif" />
</Fields>
</asp:DataPager>
</td>
</tr>
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("ID") %></td>
<td><%# Eval("FirstName") %></td>
<td><%# Eval("LastName") %></td>
<td><%# Eval("Function") %></td>
<td><%# Eval("HomeBase") %></td>
<td><%# Eval("Manager") %></td>
</tr>
</ItemTemplate>
</asp:ListView>
ListView 对象需要一个 Collection 对象,而不仅仅是一个 IEnumerable 对象。通过对结果执行 .ToList() 方法,LINQ 结果可以很容易地转换为集合。
protected void Page_PreRender(object sender, EventArgs e)
{
ListEmployees.DataSource = (from Employee emp in db4oManager.Client
select (Employee)emp).ToList();
ListEmployees.DataBind();
}
此演示中的 ListView 使用了一个(略微修改过的)分页器,该分页器由 Luis Ramirez 编写。此分页器的使用对于 LINQ for db4o 演示无关紧要。它只是一个漂亮的翻页器,如果您喜欢,可以使用任何其他翻页器。它看起来会是这样
嵌套 Repeater 演示
在另一个 Repeater 控件的 ItemTemplate 中使用 Repeater 控件就像使用任何其他控件一样简单。在内部的 Repeater 中,您将使用 Eval 语法声明要显示的数据源为子对象。对于此演示,LINQ 查询将返回一个仅包含 2 个属性的对象列表。它将返回一个类型为 Office
的属性以及一个 IEnumerable<Employee>
属性。
<table class="tableInfo">
<asp:Repeater ID="RepeaterOffices" runat="server" >
<HeaderTemplate>
<tr>
<td><h1>Office</h1></td>
<td><h1>Employees</h1></td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td valign="top"><h2><%# Eval("Office.OfficeName")%></h2><br/>
<%# Eval("Office.Street")%> <%# Eval("Office.HouseNumber")%><br/>
<%# Eval("Office.City")%><br/>
</td>
<td>
<asp:Repeater id="childRepeater" datasource='<%# Eval("Employees") %>'
runat="server">
<itemtemplate>
<%# Eval("FirstName") %> <%# Eval("LastName") %>
(<%# Eval("Function") %>)<br>
</itemtemplate>
</asp:Repeater>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</table>
为了选择数据,您的查询将变得稍微复杂一些。此 LINQ 查询使用子查询为每个办公室选择员工列表。它不是将 Office
集合作为此查询的基础,而是对 Employee
表进行分组查询。这样做的好处是,您只获得那些以该办公室作为其主基地的员工所在的办公室。(我们可以看到如何使用 group into 语法)
var hir =
from Employee emp in db4oManager.Client
group emp by emp.HomeBase
into OfficeData
orderby OfficeData.Key.OfficeName
select new
{
Office = OfficeData.Key,
Employees = (from Employee empl in OfficeData
orderby empl.LastName, empl.FirstName
select empl)
};
RepeaterOffices.DataSource = hir;
Page.DataBind();
CheckBoxList 和 Filter 演示
通常,您不想显示数据库中的每个对象。添加 where 语句就像在 SQL 中一样容易。如果您想了解更多关于如何做的信息,请查看此页面上的101 个 LINQ 示例。此演示将向您展示如何使用 LINQ 查询中的第二个集合来过滤数据。尽管访问此集合中的数据将使用不同于 LINQ for db4o 提供程序的提供程序,但查询语法看起来就像该集合是 db4o 容器的一部分。aspx 页面相当直接。这里没有什么奇怪的。
<asp:CheckBoxList ID="CheckBoxListOffices" runat="server" AutoPostBack="true"
DataValueField="OfficeName" />
<table class="tableInfo">
<asp:Repeater ID="RepeaterEmployees" runat="server">
<HeaderTemplate>
<tr>
<td>ID</td>
<td>FirstName</td>
<td>LastName</td>
<td>Function</td>
<td>HomeBase</td>
<td>Manager</td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("ID") %></td>
<td><%# Eval("FirstName") %></td>
<td><%# Eval("LastName") %></td>
<td><%# Eval("Function") %></td>
<td><%# Eval("HomeBase") %></td>
<td><%# Eval("Manager") %></td>
</tr>
</ItemTemplate>
</asp:Repeater>
</table>
编写一个使用多个 LINQ 提供程序的 LINQ 查询非常容易。在 LINQ 代码中,它看起来非常透明。此演示使用所有办公室的 CheckBoxList 来过滤 Employee
数据,并在 Repeater 中显示结果。只需选择一个或多个办公室,您就会看到所有将该办公室作为其 HomeBase
的员工。这就是您所需的所有代码。
DataManager.WithLoggingContainer(container =>
{
if (!IsPostBack)
{
// Initially we will fill the checkbox list with all the existing Offices
CheckBoxListOffices.DataSource = (from Office off in db4oManager.Client
select (Office)off).ToList();
CheckBoxListOffices.DataBind();
}
else
{
// After a postback we will show a list of employees which have an office
// in one of the selected offices.
RepeaterEmployees.DataSource = (
from Employee emp in db4oManager.Client
from ListItem off in CheckBoxListOffices.Items
where emp.HomeBase.OfficeName == off.Value
&& (off.Selected == true)
select emp).ToList();
RepeaterEmployees.DataBind();
}
});
DetailsView 演示
如果您想使用 <asp:DetailsView>
和 LINQ for db4o,则需要将 DetailsView 链接到 ObjectDataSource 控件。在 ObjectDataSource 中,您将设置数据操作所需的所有方法。该控件期望这些方法存在于 Office
类本身中。这就是为什么 Office
类具有用于 Select
、Insert
、Update
和 Delete
的静态方法。这些方法必须具有特定的签名。
<asp:ObjectDataSource ID="OfficeData" runat="server" TypeName="DataObjecten.Office"
SelectMethod="Select" DeleteMethod="Delete" InsertMethod="Insert"
UpdateMethod="Update" DataObjectTypeName="DataObjecten.Office"
EnablePaging="True">
</asp:ObjectDataSource>
<asp:DetailsView ID="OfficeDetail" runat="server" DataSourceID="OfficeData"
AllowPaging="true" CellPadding="4" ForeColor="#333333"
GridLines="None" Height="50px" Style="z-index:103;left:20px;position:absolute; top:85px"
Width="305px" AutoGenerateRows="false" DataKeyNames="OfficeName">
<FooterStyle BackColor="#5D7B9D" Font-Bold="true" ForeColor="White" />
<CommandRowStyle BackColor="#E2DED6" Font-Bold="true" />
<EditRowStyle BackColor="#999999" />
<RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
<PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
<Fields>
<asp:BoundField DataField="OfficeName" HeaderText="Office"
SortExpression="OfficeName" ReadOnly="true" />
<asp:BoundField DataField="Street" HeaderText="Street" SortExpression="Street" />
<asp:BoundField DataField="HouseNumber" HeaderText="Nr."
SortExpression="HouseNumber" />
<asp:BoundField DataField="City" HeaderText="City" SortExpression="City" />
<asp:CommandField ShowDeleteButton="true" ShowEditButton="true"
ShowInsertButton="true" />
</Fields>
<FieldHeaderStyle BackColor="#E9ECF1" Font-Bold="true" />
<HeaderStyle BackColor="#5D7B9D" Font-Bold="true" ForeColor="White" />
<AlternatingRowStyle BackColor="White" ForeColor="#284775" />
</asp:DetailsView>
这是 DetailsView 界面看起来的样子
此 DetailsView 在代码隐藏文件中没有任何代码。所有数据操作代码都包含在 Office
类中。您必须注意,如果 office 对象被传递到其中一个过程,那么它并未连接到实际的 db4o 容器。这就是为什么 Delete
和 Update
方法必须首先从数据库检索对象。数据操作必须在从 db4o 数据库检索的对象上执行。
public static List Select(int maximumRows, int startRowIndex)
{
if (maximumRows < 1) maximumRows = int.MaxValue;
var offices = new List();
offices = (from Office off in db4oManager.Client
select off).Skip(startRowIndex).Take(maximumRows).ToList();
return offices;
}
public static string Insert(Office office)
{
DataManager.WithContainer(container =>
{
container.Store(office);
});
return office.OfficeName;
}
public static void Update(Office office)
{
DataManager.WithLoggingContainer(container =>
{
// The office object that is passed to this procedure is not connected tot
// the db4o container so we first have to do that.
List<Office> offices = (from Office off in container
where off.OfficeName == office.OfficeName select off).ToList();
if (offices != null && offices.Count > 0)
{
// Then we should pass the properties from the updated object to the
// selected one.
// TODO: Maybe we could make a reflection procedure for this?
offices[0].City = office.City;
offices[0].Street = office.Street;
offices[0].HouseNumber = office.HouseNumber;
container.Store(offices[0]);
}
});
}
public static void Delete(Office office)
{
DataManager.WithLoggingContainer(container =>
{
// The office object that is passed to this procedure is not connected tot
// the db4o container so we first have to do that.
List<Office> offices = (from Office off in container
where off.OfficeName == office.OfficeName select off).ToList();
if (offices != null && offices.Count > 0)
{
container.Delete(offices[offices.Count - 1]);
}
});
}
在上面的代码中,我们进行插入而不测试对象是否已存在。在 DataManager 中,我们将 OfficeName 指定为唯一。添加已存在的 OfficeName 现在将引发 UniqueFieldValueConstraintViolationException,您可以处理它。另一个选项是检查对象是否已存在(为此,您可以使用 Update 方法中的 Select)。
TreeView 演示
TreeView 可以很好地呈现您的层级数据。在我们的例子中,我们有 Employees,每个 Employee 都有一个 Manager,Manager 也是一个 Employee。我们将为此构建一个漂亮的 TreeView。在 aspx 页面中,TreeView 的声明与其他任何 TreeView 都一样。
<asp:TreeView runat="server" ID="EmployeeHirarchy" ImageSet="Contacts" NodeIndent="10" >
<ParentNodeStyle Font-Bold="True" ForeColor="#5555DD" />
<HoverNodeStyle Font-Underline="False" />
<SelectedNodeStyle Font-Underline="True" HorizontalPadding="0px"
VerticalPadding="0px" />
<NodeStyle Font-Names="Verdana" Font-Size="8pt" ForeColor="Black"
HorizontalPadding="5px" NodeSpacing="0px" VerticalPadding="0px" />
</asp:TreeView>
通常,使用数据填充 TreeView 需要大量工作。通常,您会使用一个递归方法来执行一个循环来添加子节点。有一个替代方法。您可以为您的数据添加 IHierarchy 支持。幸运的是,Scott Piegdon 做了一个非常好的教程,解释了如何做到这一点。有关更多信息,请参阅他的文章 在自定义集合中实现 IHierarchy Support。我为 IHierarchicalEnumerable
集合创建了 EmployeeCollection
类,为 EmployeeHierarchy
类创建了该类,该类将 Employee 类作为其基类,加上 IHierarchyData
接口用于对象。我选择为 IHierachyData 实现创建一个单独的类,而不是将其嵌入 Employee 类本身。这些细节超出了本文的范围。有关这些类的更多信息,请参阅 Scott 的文章
当您将 <ASP:TreeView>
控件绑定到 IHierarchicalEnumerable
集合时,整个树将被生成。在我们的例子中,这意味着对于每个 Employee
,都会执行一个查询来查找其子对象。当我查看日志文件时,我注意到构建树会执行大约 300 个查询。在我的机器上大约需要 20 秒。当然,这种性能是不可接受的。幸运的是,通过执行 1 个查询来检索所有对象,然后从内存中执行查询而不是查询数据库,可以轻松解决此问题。为此,我创建了一个 EmployeeCache
类。现在页面可以在 2 秒内显示。
对于 IHierarchy 代码,我需要几次将 LINQ 查询的结果转换为其他类型,因此我创建了一个扩展方法,该方法可以在 EmployeeHierarchyExtensions
类中找到。
为 IHierarchy 支持创建的代码现在已经相当多了。好消息是,您现在可以在需要具有 IHierarchy 支持的对象的地方使用它。代码也非常通用,并且非常容易移植到其他对象。现在,我们在 aspx 页面中需要做的就是
if (!IsPostBack)
{
var collection = (EmployeeCollection)(from EmployeeHierarchy emp
in EmployeeCache.Employees
where emp.Manager == null select emp).ToEmployeeCollection();
EmployeeHirarchy.DataSource = collection;
EmployeeHirarchy.DataBind();
EmployeeHirarchy.CollapseAll();
}
结论
db4o 的 LINQ 使用起来非常简单且功能强大。学习曲线非常低。本文仅触及了 db4o 可能性的表面。如果您试图将其与您现在使用的 RDBMS 进行比较,那么您绝对必须考虑到
- 性能非常好。根据这个基准测试,它是最好的之一。
- db4o ObjectContainer 中的所有工作都是事务性的。(手动回滚和提交可用。)
- db4o 的复制工具使 db4o 具有可伸缩性。
- db40 数据库可以客户端/服务器模式访问。这可以通过嵌入式客户端/服务器或网络客户端/服务器来完成。
- 有一个对象管理器工具可用于浏览和查询 db4o 数据库。虽然免费版本不能用来构建查询,但它是浏览数据库的好工具。
- 您可以控制并发和锁定。
参考文献
- db4objects
- db4o 开发者社区
- LINQ 入门
- LINQ 项目
- 101 个 LINQ 示例
- ScottGu 的博客
- 面向对象的数据库编程与 db4o
- 将 LINQ 与 ASP.NET 结合使用
- 在自定义集合中实现 IHierarchy Support。
- LINQ 提供程序
- Google 数据分页器
历史
此演示创建于 2008 年 4 月,用于我为我工作的公司(Mirabeau)举行的一次演示。
5 月 9 日:更改了 db4o 管理器,使 Server 实例在应用程序期间保持活动状态。我还添加了一个全局 Client 引用,以便我们可以直接在 LINQ 查询中引用 db4o 容器,而无需显式实例化容器。现在,访问 db4o 数据库所需的代码更少了。