There are some very nice additions to the new C# 3.0 language. For instance Automatic Properties, Object and Collection Initializers, Extension Methods, Lambda Expressions, Anonymous Types and of course LINQ.
As a developer you probably often write code for accessing data with OLEDB, DAO, ODBC, ADO or ADO.NET. Or maybe you are using an object relational mapper like NHibernate or even ActiveRecord. You probably noticed that each time you needed more coding than you wanted. In this demo application you will see that accessing your data will never be as easy as with db4o.
Db4objects has now joined the power of querying data with LINQ with the ease of accessing data with db4o. Since February 2008 db4o version 7.2 beta has become available (the production release is now available). This version contains a LINQ provider for db4o.
LINQ will probably be the new Visual Studio 2008 feature that will speak most to your imagination. With LINQ you can use SQL like syntax inside your code for retrieving various kinds of information. By now there are already a couple of dozen LINQ providers. A LINQ provider is the code that lets you query a specific type of data. Visual Studio has a LINQ provider for objects (LINQ), SQL (DLINQ) and XML (XLINQ). Searching the internet you will find many more providers, for instance for Excel, Flickr, Google, Sharepoint, WMI, etc. The cool thing is that the query syntax is the same for every provider. You can even query multiple providers in one query.
The LINQ query syntax looks very much like SQL. The biggest difference is the command order. You will start with the 'from' statement. The reason for this is that intellisence is able to give you hints the moment you have specified the object in the from statement.
var result =
from p Person
where p.Age > 50
select p;
Db4o is an open-source native Object-oriented Database Management System (ODBMS) available for both .NET and Java platforms. As a native ODBMS, the database model and application object model are the same, hence, no mapping or transformation is required to persist and query objects with db4o. Regarding usage mode, db4o can be deployed either as a standalone database or a network database. Finally, db4o has support for the schema evolution, indexing, transaction and concurrency, database encryption, and replication service (among db4o databases and certain relational databases). At this moment the latest version of db4o is 7.2 and available under two licenses: GPL and a commercial runtime license.
In February 2008 db4objects released beta version 7.2 of db4o. This was the first release that included the LINQ provider for db4o. Now 2 months later the 7.2 version has been release as a "production" release and an optimized version 7.3 beta is now available. Db4o was already quite easy but now with LINQ for db4o you can do it using standard query syntax instead of finding out how to use db4o's native query syntax. Querying data in a db4o database with LINQ will feel like you are querying data that is already available in your application. You do not have to make object relational mappings and you do not have to use a heavy data access layer. With LINQ for db4o you can do everything with only a couple of lines of code. Linq expressions are analyzed and optimized down to db4o's fast querying interface: SODA. Whenever a suitable translation cannot be found the query executes as a plain Linq for Objects query (a predicate that is tested against every object of the specified type).
For showing you the most basic operations this demo solution includes a very simple console application. This demo creates 2 new objects and stores them in a db4o database. Before and after each save operation a list of all available objects is written to the console. Here is the entire code:
using System;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
namespace ConsoleDemo
{
class Program
{
static void Main(string[] args)
{
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));
}
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
The output of this program will be:
When you run this demo for the 2nd time, then at the end of the demo there will be 4 objects in the database instead of 2. If you declare a new object in code and you save that object to db4o then it will also be a new object in the database. If you want to rerun this demo more often, then just uncomment the line tat deletes de database file.
One other thing that comes to mind very quickly is the question what will happen if you change your object. Just try it out by uncommenting the Age
property and the console write that will display that property (make sure that the file delete statement is commented out). As you can see the application keeps on working. The old objects will show the default value.
This demo is not meant to be an exhaustive presentation of all the capabilities of LINQ or db4o. Instead I want to show you how easy it is to use in an ASP.NET website. (using it in a winforms application is just as easy.). This demo will show you how to use LINQ for db4o with some popular .NET databound objects. Every demo will be shown in its own .aspx page and can be executed without the need of the other pages.
The data model for this demo is very simple. There are only 2 objects with a couple of properties. We have an Employee
object and an Office
object. The 2 objects are related to each other through the property HomeBase
in the Employee
object which is of type Office
. Because the property is of the type Office
it is declaring a relation between the two objects. The Employee
object also has the property Manager
which is of type Employee
. This property declarers a hierarchical relation to itself.
Both objects are also overwriting the method ToString
to make it easy to show what an object instance is about. I only gave the Office
object the methods Select
, Insert
, Update
and Delete
. These methods are there so that the object could be used in the DetailsView demo
If you want to use Linq for db4o then you need to add at least 2 references. Db4objects.Db4o is the core library for db4o. This one is always necessery. For using Linq for db4o you will also need the Db4objects.Db4o.Linq library. There are Linq query constructions that need 2 additional libraries. This was the case with the TreeView demo. The libraries that are also needed are Cecil.FlowAnalysis and Mono.Cecil. To bad that LINQ for db4o only gives you a warning about the need of these libraries at runtime. All of these libraries are part of the db4o v7.2 download. Besides those libraries I also added the log4net library. The DataManager is using log4net for creating the log file.
Of course like any other data storage you still have to tell what database to use. The time will also come quick that you want to be able to say how the data storage should be used. And then we also want to do that on one central location. For this I created the static class db4oManager
. If you do not need special configuration or logging, then this class only needs to be a couple of lines of code. Here is the most basic version of that class. The WithContainer
method is set up in such a way that it is easy to use with a Lambda Expression. The demos will show you how to use this method.
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);
}
}
}
The bad news is that this won't work when you get more than a couple of visitors to your website. If you reference a container by executing the OpenFile
method then the file will be locked the moment you write to it. This is OK for most client applications but not for most web applications. Fortunately db4o has a Client-Server mode for overcoming this problem. This could also easily be extended to a Networked Client-Server application where the server could be on a different machine. Here is the code for the db4oManager
where the db4o database is accessed using the Embedded 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)
{
IObjectServer server = Db4oFactory.OpenServer(config, FileName, 0);
try
{
using (var container = server.OpenClient())
{
action(container);
}
}
finally
{
server.Close();
}
}
}
This Embedded Client-Server mode is still not optimal. It could easily be optimized by managing the server at the application level or even a read only client at the application level. In the latest version (May 9) of this demo I added static properties for this which are set in the static constructor. We can now even use the db4oManager.Client directly in our LINQ queries without using the WithContainer construction. See the demos below how to use this. Here is the code for the constructor that creates the client object container that we will use.
static db4oManager()
{
Server = Db4oFactory.OpenServer(config, FileName, 0);
log.Info("The Server instance has been created.");
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; }
You do have to be aware that all actions in an container will be handeled as one transaction. So you only can use this db4oManager.Client
for read only access. If you want to manipulate data, then use the WithContainer
construction. If you want te see an other implementation of a Client-server configuration, then look at the httpHandler that is part of the Db4oSiteCS.vsi template project that can be found at the page: Starter Kit - Web Applications With db4o.
Like most databases you will probably also want to specify indexes and constrains. For most constraints you should just add validation code to your property setters. For declaring the Indexes and unique value constraints the static constructor of the DataManager
class is a very nice location. In the code below you will see a strange field notation. This is because the properties are declared using the new C# 3.0 syntax and we do not have private variables. Besides that the declaration is straight forward.
static db4oManager()
{
config.ObjectClass(typeof(Employee)).ObjectField("<ID>k__BackingField"
).Indexed(true);
config.Add(new UniqueFieldValueConstraint(typeof(Employee), "<ID>k__BackingField"));
config.ObjectClass(typeof(Office)).ObjectField("<OfficeName>k__BackingField"
).Indexed(true);
config.Add(new UniqueFieldValueConstraint(typeof(Office),
"<OfficeName>k__BackingField"));
}
The DataManager
class in the demo will also show you how to react on events that de db4o manager raises. Discussing these events is out of scope for this publication. If you want to see more, then have a look at the WithLoggingContainer
method. You can also look for more information about db4o events in the db4o reference pages about the Event Registry API. For managing your logfiles there is no better tool than log4net. A nice intro by David Salter about log4net can be found here.
All the demos can be accessed from the Default.aspx page. Besides links to the demos and links to various sources the Default.aspx page also has 2 buttons. One for deleting the database and an other one to create the database and fill it with dummy data. The Default.aspx page will also show the number of offices and employees in the database. Below you will see the demos explained.
You can directly bind a <asp:GridView>
to a LINQ for db4o result without any additional coding. Just put a GridView control on your ASP.NET page like this:
<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>
You then only need the following lines of code for binding the GridView to all the Employees in the db4o database.
protected void Page_PreRender(object sender, EventArgs e)
{
GridPerson.DataSource = from Employee emp in db4oManager.Client select emp;
GridPerson.DataBind();
}
One of the new controls in ASP.NET 3.5 that I think will be very popular is the <asp:ListView>
control. The ListView control supports the data editing, insertion, deleting, paging and sorting semantics of higher-level controls like the GridView. But - unlike the GridView - it provides you with complete control over the html markup generated. This demo will not do any data editing. You can see how to do data editing in the DetailsView demo.
<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>
The ListView object needs a Collection object and not just an IEnumerable object. A LINQ result can very easily be converted to a collection by execution the .ToList() method on the result.
protected void Page_PreRender(object sender, EventArgs e)
{
ListEmployees.DataSource = (from Employee emp in db4oManager.Client
select (Employee)emp).ToList();
ListEmployees.DataBind();
}
The ListView in this demo is using a (slightly modified) pager that is written by Luis Ramirez. The usage of this pager is of no concern for the LINQ for db4o demo. Its just a nice looking pager and if you like you could use any other pager. This is what it will look like:
Using a Repeater control inside the ItemTemplate of an other Repeater control is as easy as using any other control. In the inner repeater you will declare the datasource to be the child object using the Eval syntax. For this demo the LINQ query will return a list of objects with only 2 properties. It will return a property of type Office
plus a IEnumerable<Employee>
property.
<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>
For selecting the data your query will become a little more complex. This LINQ query is using a subquery for selecting the list of employees for every office. Instead of using the Office
collection as the base for this query it is doing a group query on the Employee
table. The benefit of this is that you only get the Offices where there are employees with that office as its homebase. (and we can see how to use the group into syntax)
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();
Usually you do not want to show every object in your database. Adding a where statement is as easy as it would be in SQL. If you want more information about how to do that, then have a look at this page with 101 LINQ samples. This demo will show you how to use a second collection in the LINQ query for filtering data. Although accessing the data in this collection will be done using an other provider than the LINQ for db4o provider, the query syntax will look like the collection is part of the db4o container. The aspx page is quite straightforward. There are no strange things here:
<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>
It is very easy to write one LINQ query that uses multiple LINQ providers. This will look very transparent in the LINQ code. This demo uses a CheckBoxList of all the offices for filtering the Employee
data and it will show the result in a Repeater. Just select one or more offices and you will see all the employees that have that office as their HomeBase
. This is all the code you need:
DataManager.WithLoggingContainer(container =>
{
if (!IsPostBack)
{
CheckBoxListOffices.DataSource = (from Office off in db4oManager.Client
select (Office)off).ToList();
CheckBoxListOffices.DataBind();
}
else
{
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();
}
});
If you want to use a <asp:DetailsView>
with LINQ for db4o then you need to link the DetailsView to an ObjectDataSource control. In the ObjectDataSource you will set all the methods that are needed for data manipulation. The control expects the methods to be in the Office
class itself. This is why the Office
class has static methods for Select
, Insert
, Update
and Delete
. These methods must have a specific signature.
<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>
This is what the detailsview interface will look like:
This details view does not have any code in the code behind file. All data manipulation code is part of the Office
class. You do have to be aware that if the office object is passed to one of those procedures, that it is not connected tot the actual db4o container. This is why the Delete
and Update
methods fist have to retrieve the object from the database. The data manipulation must be performed on the object that is retrieved from the db4o database.
public static List<office> Select(int maximumRows, int startRowIndex)
{
if (maximumRows < 1) maximumRows = int.MaxValue;
var offices = new List<office>();
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 =>
{
List<Office> offices = (from Office off in container
where off.OfficeName == office.OfficeName select off).ToList();
if (offices != null && offices.Count > 0)
{
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 =>
{
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]);
}
});
}</office></office>
In the code above we are doing an insert without testing if the object already exists. In the DataManager we specified the OfficeName to be unique. Adding an already existing OfficeName will now throw an UniqueFieldValueConstraintViolationException which you could handle. An other option would be to check if the object already exists (for this you could use the select that is in the update method).
A treeview can give you a nice presentation of your hierarchical data. In our case we have Employees each having a manager which is also an Employee. We will build a nice treeview for this. In the aspx page the treeview is declared just like any other 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>
Populating a treeview with data is usually quite some work. Usually you will use a recursive method that executes a loop for adding child nodes. There is an alternative. You could add IHierarchy support to your data. Fortunately Scott Piegdon made a very nice tutorial that explains how to do that. For more information have a look at his article Implementing IHierarchy Support Into Your Custom Collections. I created the EmployeeCollection
class for the IHierarchicalEnumerable
collection and the class EmployeeHierarchy
which has the Employee class as its base class plus the IHierarchyData
interface for the objects. I choose to make s separate class for the IHierachyData implementation instead of embedding it within the Employee class itself. The details for this are out of scope for this article. For more information about those classes, please have a look at Scott's article
When you bind a <ASP:TreeView>
control to an IHierarchicalEnumerable
collection then the entire tree will be generated. In our case this means that for every Employee
a query is executed to look what the child objects are. When I looked at log file, I noticed that that building the tree will execute about 300 queries. On my machine that took about 20 seconds. Of course this performance is unacceptable. Fortunately this could easily be fixed by doing 1 query that retrieves all the objects and then executing the queries on the objects in memory instead of querying the database. For this I created an EmployeeCache
class. Now the page shows within 2 seconds.
For the IHierarchy code I needed to convert the result of a LINQ query a couple of times so I created an extension method that can be found in the EmployeeHierarchyExtensions
class.
The code created for the IHierarchy support is now considerable. The good thing is that you can now use is wherever you need an object with IHierarchy support. The code is also quite generic and is very easy to port to other objects. The only thing we now have to do in the aspx page is:
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();
}
LINQ for db4o is extremely easy to use and very powerful. The learning curve is very low. This publication only scratched the surface of the possibilities of db4o. If you are trying to compare it with the RDBMS that you now use then you definitely have to take into consideration that:
- The performance is very good. According to this benchmark its one of the best.
- All work within db4o ObjectContainer is transactional. (Manual rollback and commit are available.)
- The replication tool for db4o makes db4o scalable.
- The db40 database can be accessed in Client-Server mode. This can be done using an embedded Client-Server or a Networked Client-Server.
- There is an object manager tool available for browsing and querying a db4o database. Although you can't build queries with the free version its a nice tool for browsing your database.
- You can control Concurrency and locking.
- db4objects
- db4o Developer Community
- LINQ introduction
- LINQ Project
- 101 LINQ samples
- ScottGu's Blog
- Object-oriented database programming with db4o
- Using LINQ with ASP.NET
- Implementing IHierarchy Support Into Your Custom Collections.
- LINQ providers
- Google data pager
History
This demo was created April 2008 for a presentation that I gave for the company that I work for (Mirabeau)
May 9 : Changed the db4o manager so that the Server instance will stay active during the application. I also added a global Client reference so that we could just reference the db4o container in a LINQ query without explicitly instantiating a container. We now even need less code for accessing a db4o database.