|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Content
IntroductionThere 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. So what is LINQLINQ 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; And what is db4oDb4o 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. The power of LINQ for db4oIn 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). The very basics of db4o (and LINQ for db4o)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)
{
// 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; }
}
}
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 About the website demoThis 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 modelThe data model for this demo is very simple. There are only 2 objects with a couple of properties. We have an Both objects are also overwriting the method The referencesIf 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. Managing the db4o databaseOf 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 //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);
}
}
}
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 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() { // 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; } You do have to be aware that all actions in an container will be handeled as one transaction. So you only can use this 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 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"));
}
Logging using log4netThe The demosAll 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. A GridView demoYou can directly bind a <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();
}
A ListView demoOne of the new controls in ASP.NET 3.5 that I think will be very popular is the <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:
A Nested Repeater demoUsing 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 <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 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();
A CheckBoxList and Filter demoUsually 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 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();
}
});
A DetailsView demoIf you want to use a <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 public static List
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 demoA 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 When you bind a 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 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();
}
ConclusionLINQ 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:
References
HistoryThis 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.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||