Click here to Skip to main content
Click here to Skip to main content
Articles » Database » Database » General » Revisions
 

Kerosene ORM: a dynamic, self-adaptive and configuration-less ORM with no need for configuration files

, 6 Sep 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
The fifth incarnation of a dynamic and self-adaptive ORM library with full support for POCO objects, that doesn’t require any mapping or configuration files, and with support for natural SQL-like syntax in plain C#
This is an old version of the currently published article.

Introduction

There are lots of scenarios, even in these days plenty of open-sourced frameworks and entities, where the time and efforts you need to devote to master, write and maintain the plethora of things most ORM solutions require don’t really worth.

Don't misunderstand me: they are extremely valuable and provide a huge number of advantages, indeed. But I’m also considering a whole range of scenarios where their overhead is, maybe, too much. For instance:

  • They typically tend to be a complex environment to understand and operate with. They require you to master a whole range of tools, configuration files, and languages in order to be productive. And even the simplest solution easily becomes hard to maintain, even if you are using automatic code generation tools.
  • It also happens that most of those frameworks require you to modify your business classes with database related stuff. Even if those modifications are in the form of attributes they break, in my modest opinion, the principle of separation of concerns. It can be definitely worse in those cases where you don’t have access to the source code, because then you typically end up writing wrappers classes, and struggling to maintain the overall coherence.
  • Typically you have not very much control on the actual SQL code they generate, ending up executing really fat code against your database. Not to mention that you need to follow the strict C# syntax instead the more flexible SQL one.
  • And, finally, the hidden assumption that you have a stable and controlled environment, all from the database schema to those configuration files and your source code, because as soon as the tiniest piece change you will need to recreate them all, or almost, and pray that no changes will break your solution.

The Kerosene library has been built to deal with these scenarios and much more. It is the fifth incarnation of the dynamic, self-adaptive, and configuration-less ORM library that, with a creative use of C# 4.0 dynamics, generics, and lambda expressions, provides you with an easy and powerful mechanism to deal with your database. You can using straight C# code write a natutal SQL-like syntax, even when the C# compiler is not supposed to accept it. You will not have to write or maintain external configuration files. And it is built to provide a complete support for POCO objects, so you don’t ever have to write any wrapper of interface class, or altering in any way your business objects.

What’s new

The first thing to notice is the change of its name. Its previous one was Kynetic (being a contraction for "dynamic" and "kinetic") but, as the industry juggernaut chose a similar name for a popular platform, I have suffer since then a lot of misunderstandings (and jokes). So, exercising the privilege of being its author, I have renamed it as Kerosene – basically because this name permits me to keep the "K" prefix for all its classes, and because the meaning of fueling the development of your applications.

While maintaining the improvements achieved in version 4.5, the topics for version 5 have been about a better performance, an improved dynamic parsing mechanism, and a cleaner and simplified overall architecture and syntax usage. Indeed, some old users may miss some methods, but they are no longer needed due to the improvements mentioned.

Finally, one major change is that the former Maps mechanism has been deprecated, but substituted with a more powerful and advanced architecture that, indeed, has become an entity framework. A very nice feature is that, unlike other solutions, and keeping with the spirit of simplicity of Kerosene, it is completely built to work with your POCO classes, without requiring any modifications in your business classes to use it, nor have you to write any external configuration or mapping files.

What is this article for

This article is an introductory tutorial of Kerosene. It does not cover advanced scenarios, as the client-server WCF version, the details of the mapping mechanism, the internals of Kerosene, or the details on how to expand it to support other databases than the one included in the download (for Microsoft SQL server). All those items are covered in the articles that appear in the references section at the end.

Kerosene Basics

Using Kerosene is extremely easy. It has been designed precisely to permit you to forget all those details and nuances that, otherwise, you have to take into consideration when using other ORMs. And to be flexible enough to adapt to whatever scenario it may have to face, even those where you only have minimal information available about the database.

To prove these points let’s see our first example. Let’s suppose that you only know that your database has a table named "Employees" with a column named "LastName" containing strings. Let’s now suppose that you are interested in obtaining a list of all the employees whose last name starts with "C" or bigger. Open your compiler because, believe it or not, what follows is all the code you need:

var link = new KLinkDirectSQL ( "your connection string" );
var cmd = link.From( x => x.Employees ).Where( x => x.LastName >= "C" );
foreach( var obj in cmd ) Console.WriteLine( "Record: {0}", obj );

Yes, that’s right; this is all you need to write! No external configuration or mapping files. No detailed knowledge of your database’s schema. No modifications of any business class or wrappers. We have been able to use a SQL-like syntax that has even allowed us to compare two string-alike elements using regular C# operators (something not even supported by the C# compiler). And, on of top of that, as expected, no need to worry about creating, opening, and closing any connections to the database.

Let’s see what we have done on those three lines:

  • In the first line we have created a "Link" object. They are used to maintain all the details about how to connect to the underlying database, how to open and close the connections to it when needed, how to interact with the transactional mechanism, etc. In the example above we have instantiated a specialized link object for Microsoft SQL Server databases, but Kerosene is agnostic and it is built to be extended with support for other databases as needed. Also note that, in this case, we have used a regular connection string as if you were going to use a standard ADO.NET connection object.
  • In the second line we have created a Query/Select command. A command is just a class that helps you to build the specific SQL command you want to use. As you can expect, there are also specialized versions for the Insert, Delete and Update operations. And, finally, there is also a version for "Raw" commands, which are those that let you write directly the text of whatever SQL statement you want to execute, including stored procedures.
  • We have chained all the methods in a fluent syntax manner. We have also used "dynamic lambda expressions" to specify both the table and the logical condition. A "dynamic lambda expressions" is defined as a lambda expression where at least one of its parameters is a dynamic one. This way we are able to avoid the early binding mechanism, and so, able to write those conditions using a syntax closer to the SQL one.
  • These dynamic expressions are parsed and their contents are annotated to generate the actual command's text (see the DynamicParser article in the references section at the end for more details). The parameters found are extracted and stored for future use, in order to avoid injection attacks.
  • Finally, in the third line, we have executed our command and iterated through its results. By default Kerosene returns those results in the form of "dynamic records", instances of a specialized class that adapts itself dynamically to whatever results are returned, and that permits you to manipulate them in any way you may need.

More explanations

Although the above are, actually, the most important things to understand in order to use Kerosene, what follows provides you with a broader vision in order to allow you to both face more complex scenarios and to understand a bit of what’s going on behind the scenes.

In order to do that, I’m going to assume that we are dealing with a (minimalist) HR system composed by three tables: "Employees", "Countries" and "Regions", as shown in the following SQL model (the code in the download contains the scripts to generate this model and to load a set of initial contents):

It happens that, in this scenario, all those tables contain a key column named "Id", but this has happened by chance: we have used this just for our convenience as we were thinking in terms of the business rules and not in terms of what rules the ORM tool needs us to follow.

Let me reinforce this: unlike other popular frameworks and tools, Kerosene does not require you to use any specific names on your class members or on the columns in your database. What's more: it does not even require you to know in advance the schema of your database, or what column is your key one, or columns if you are using a composite key. Eventually your table may not have any key column at all, but even in this scenario Kerosene will work perfectly.

Now, to make things a bit more interesting, each Employee record is assigned to a given Country through its "CountryId" column, and can be assigned to a manager if its "ManagerId" column is not NULL. Also, each Country is assigned to a given Region through its "RegionId" column, and each Region can be assigned to a parent Super-Region if its "ParentId" field is not NULL (in a hierarchical way).

By the way, Kerosene makes no differences between tables and views. So the above tables can be views if you prefer this approach, for instance because security reasons. It is up to you to specify the appropriate connection string to access the tables or views you want to use.

Enumerating the results

In order to obtain the results of your operations you can either use the enumeration capabilities of the commands you have built, as in the above example, or you can just execute them to obtain the number of records affected by the operation. Let’s see first the enumeration capabilities.

All commands in Kerosene are enumerable ones. When enumerated, as in the example above, they return the results in the form of arbitrary objects whose specific types will depend upon how you have decided to build them. This way you can obtain back instances of the default type Kerosene returns or, through converters or maps, instances of whatever your business objects' types might be.

By default those objects are instances of the class KRecord, a dynamic class that dynamically adapts itself to any schema returned from the database. Its instances will contain a dynamic set of columns to hold the columns read from the database. And you can access those columns by either using a dynamic syntax or by using an indexed one.

The beauty of this approach is that it doesn’t matter what schema your database has, or what specific schema the results will be based upon: the records will adapt themselves dynamically by generating the appropriate set of columns in each execution.

So, for instance, if the schema of your database changes, as far as the names of the columns you are using remain the same, your code won’t break. And you get it for free without having to maintain any external configuration or mapping files.

Now, in order to discuss how to access the contents returned from the database, let's see first another example:

var cmd = link
  .From( x => x.Employees.As( x.Emp ) )
  .From( x => x.Countries.As( x.Ctry ) )
  .Where( x => x.Emp.LastName == "C" )
  .Where( x => x.Or( x.Emp.Id != null ) )
  .Select( x => x.Ctry.All() )
  .Select( x => x.Emp.Id, x => x.Emp.FirstName, x => Employees.LastName );

foreach( dynamic obj in cmd )
   Console.WriteLine( "Id:{0} First Name:{1} Last Name:{2}", obj.Emp.Id, obj.FirstName, obj.Employees.LastName );

In this example we have also used a Query/Select command, but in this case obtaining the results from several tables using aliases. We have also chained several Where conditions, and finally we have specified what columns we wished to select. Don't worry about this details now, we will discuss them later.

Let us now focus again on how have we access the contents in the foreach loop.

We have used three ways: the first one, used to access the "Id" of the employee, uses the table alias to qualify the column name. A column name qualification is needed here because there are two "Id" columns, in the Employees and Countries tables, and we need to differentiate between them. The second one, used to access the "FirstName" column, uses no table qualification at all: in this case this is the only column with this name so there is no ambiguity. And the third one, used to access the "LastName" column, uses the table name instead of its alias to qualify the column name: as you can see, it is completely equivalent to use the alias or the table name.

This dynamic approach is very convenient and elegant, and I use it extensively. But we can all agree in that by using dynamics it won't be precissely the fastest way on earth. For this reason the KRecord class does also support an indexed way to access its columns, as we can see in the following code:

foreach( dynamic obj in cmd )
   Console.WriteLine( "Id:{0} First Name:{1} Last Name:{2}", obj["Emp","Id"], obj["FirstName"], obj["Employees","LastName"] );

The above discussion about how and when to qualify a column name applies here as well. Depending upon my mood and needs I mix the two syntaxes freely – you can choose which one you are more comfortable with.

Discussing how we wrote the command

As promissed we are going now to come back to the details of the command we wrote above. In order to understand the following discussion better, let's first take a look at the actual SQL code the above command will produce:

SELECT Ctry.*, Emp.Id, Emp.FirstName, Employees.LastName FROM Employees AS Emp, Countries AS Ctry WHERE (( Emp.LastName = @p0 ) OR ( Emp.Id IS NOT NULL ))

You can always obtain the SQL code a given command will produce by using its CommandText(...) method, that will return the iterable version or the non-iterable one, depending the value of its boolean argument. If you want to append the parameters the command will use, you can use the TraceString(...) extension method instead.

Now, the first thing to note is that, by design, Kerosene won’t generate any SQL code you have not written specifically. There will be no surprises and no fat code will be injected without your control. What's more, Kerosene will never execute anything you have not told it explicitly to execute, being the major example opening or committing transactions. The only exception to this rule is that, if there is not an active connection when the command gets executed, a connection is created, opened, and closed afterwards on your behalf.

If you now take a second look at the C# code of the example above, we can discuss the following interesting points:

  • In this case we have instantiated the command using the "From(...)" extension method of the IKLink interface. It is a convenient way to do it, instead of using its constructor, but both ways are completely equivalent. And, yes, there are similar extension methods for the other operations as well.
  • We have heavily used lambda expressions to write the command. Yes, Kerosene uses them almost in every place. This is how it reaches its extraordinary flexibility when writing the operations you want to execute. If you prefer to write your own SQL text... don’t be concerned: Kerosene does also provide this capability, in the form of Raw commands, as we will see later.
  • We have chained several times the same methods. Yes, it doesn’t matter how many times you use a given method or in what order: Kerosene is prepared to accommodate to your way of thinking, and not forcing you to the other way around. Each method basically annotates the information it needs and, when the time comes, the command will use it to generate the SQL code.
  • We have used aliases for the tables used in the command. We had to use them because, remember, we had columns named "Id" in each of them, and without aliases we will receive errors from the database engine. The way we have specified them was by using the "As(...)" dynamic extension method attached to the table name we wanted to qualify. It accepts a single parameter in the form of a dynamic lambda expression that specifies the alias to use for the table the method is attached to.
  • We have used an OR condition in the Where statement by using the syntax "x => x.Or(...)" when writing the condition. This is because, otherwise, and by default, the logical conditions are chained using an AND operator. Note that, whereas the above "As(...)" method is available all across Kerosene, the "x.Or(...)" and "x.And(...)" ones are specific for the Where clauses only. They accept a single argument, a dynamic lambda expression that specifies the logical condition to add to the Where clause.
  • And finally, please note how we have specified the columns to select. Either we can specify them by their names, or, as in the first "Select(...)" method, we can specify all the columns in a given table by attaching the "All()" dynamic extension method to the table’s name. Also note that, as expected, if you don’t use any "Select(...)" methods this is equivalent to a "SELECT *" statement.

Results can be NULL

Unlike other ORMs Kerosene returns NULL when there are no more records, or when no records were found. But it does not raise any exceptions in those circumstances. In Kerosene exceptions are raised only when something went wrong – and, in my modest opinion, an empty result set is not wrong per-se, but conceptually a very valid possible result.

The obviuos drawback of this approach is that any result obtained from the database should materialize in an instance of a class, and not in an instance of a struct or a value object. I have not felt this as a major restriction at all but in 10-3% scenarios, so this is the way I designed Kerosene to work.

Records are forgotten

It is also important to note that, in this basic mode of operation, once a record is returned it is just "forgotten": Kerosene won’t keep track of them. It is the application’s responsibility to do so if needed. The rationale behind this approach is that, when using this basic mode of operation, you know what you are doing and you will deal with the records in the most appropriate manner. In this case, Kerosone’s responsibility is just to provide with an easy way to interact with the persistence layer.

On the flip side, if you are using the new Maps entity framework included in Kerosene this won't be true: Kerosene will keep track of your entities and their states, letting you operate on them in a natural and handy way. But we will see a bit more about Maps later below.

Comparison operators

One major idea behind Kerosene was to decrease the mismatch between SQL code and C# one. In order to achieve it, its parsing engine is programmed to understand all major C# operators and translate them into their equivalent SQL ones, regardless of the types used, and without the need to use a different set of methods depending upon which C# type we are dealing with.

This is why we were able to write "x => x.FirstName >= "C"" that, you will surely agree, is more natural and closer to the SQL syntax that its pure-C# equivalent. It is possible because, by using dynamics, we are implicitly using late-binding and so postponing the validation of the existence of methods and properties till run-time, which is precissely what we want in this case.

Finally one quick note: the equality comparison operator to use shall be the standard "==" one, and not the assignation "=" one (as if you have a deep SQL inheritance you were probably more prone to use). Despite that, in some cases, both will work, it is not considered a best practice to mix their meanings.

More on Enumerable and Non-Enumerable commands

We have mentioned before that all command objects in Kerosene are enumerable ones. This is because all of them implement the IKCommandEnumerable interface. What is nice is that, besides providing those enumeration capabilities we have used so far, there are a number of handy extension methods you can use with this interface as well:

  • ToList(), that returns a list (potentially empty) with all the records returned from your command’s execution.
  • ToArray(), a similar one but returning an array of objects.
  • First(), that returns the first objects as it was returned from the database, or NULL if there are no records returned.
  • Last(), that returns the last one or NULL. Note that the generic implementation of this method gathers all the records from the database discarding them until the last one is found, so potentially implying a lot of network traffic. The assumption is that either you rewrite your command with a different sort order, or you override this method for your specific database.
  • SkipTake(), that discards the first "skip" records and then permits to enumerate at most the next "take" ones, or null if there are not more records available.

So, for instance, to obtain just the first record from a query you can write something like this:

var cmd = link.From( x => x.Employees ).Where( x => x.LastName >= "Smith" ).OrderBy( x => x.Id );
var obj = cmd.First();
There is also another interface, the IKCommandExecutable one, which almost all commands in Kerosene implement (actually all except the Query/Select ones). It defines the method Execute() whose mission is executing the command and returning the number of rows affected. Let’s see the following code:
var cmd = link.Delete( x => x.Employees ).Where( x => x.Id == "007" );
int n = cmd. Execute();
Console.WriteLine( "\n>> Rows affected: {0}", n );
cmd.Dispose();

The Execute() method is available on Insert, Update, Delete and Raw commands. There is really nothing else to say about it.

Parameters and Kerosene

When parsing the dynamics expressions any objects that are not dynamic are considered to be a parameter of the expression. They are extracted from the parsed text and substituted with the name of the KParameter instance that will store their value. This class is specialized in storing the values of your parameters in an agnostic way, not dependent on your specific database.

There are two exceptions to this mechanism: the scape syntax that we will see later, and the NULL values. NULL values are not converted into parameters because, if doing, so we can end up with parsed expressions that are not syntactically correct from the SQL language perspective. So when a given object’s value is null, it is translated into the appropriate "NULL", "IS NULL", or "IS NOT NULL" literals depending upon the circumstances.

When a given value is "captured"? Quick answer: when the dynamic lambda expression is parsed (aka: "executed"). For instance, in the next example, in each iteration the Where condition is re-evaluated and a new string is generated to compare against:

for( int i = 0; i < 5; i++ ) {
   var cmd = link.From( x => x.Employees ).Where( x => x.Id > i  );
}

The same logic applies when you are using values stored in external variables (aka: a closure), or obtained from the invocation of C# methods.

Again I encourage you to take a look at the DynamicParser class' discussion in the article that appears in the references section below.

Transforming your Classes into Parameters

It may very well happen that your code is using objects that are not related to any type your database can understand: you would like to use your own types and values instead of being restricted to the built-in ones in your database.

For the sake of this discussion let’s suppose you have a class named ClockTime whose mission is to store given moments in a day. You wish to use it instead of the C# DateTime class. Let’s also suppose that your Employees table contains a column named "ShiftStart" that contains the time the employee’s shift starts. If you want to find all the employees that work in the late shift you could use something like what follows:

var start = new ClockTime( 16, 0, 0 );
var cmd = link.From( x => x.Employees ).Where( x => x.ShiftStart >= start );

The problem is that the underlying ADO.NET engine doesn't know what to do with the instance of your own type, the ClockTime class. Kerosene is prepared to solve these situations. You just have to indicate in advance how those types must be translated into types that your database (actually, the ADO.NET engine) can understand. This is achieved by registering a "transformer" for each custom type you'd like to use, as follows:

link.AddParameterTransformer<ClockTime>( x => x.ToString() );

The AddParameterTransformer<T>(...) method merely annotates what delegate Kerosene needs to invoke to transform the parameters of type "T" into whatever thing is accepted by your database. In the example, we are transforming our type a string, just for simplicity, but you can create any instance of any C# type that ADO.NET is able to understand.

Note also that this mechanism only kicks-in when transforming parameters before the execution of the command. The values returned in the columns after the execution are always the C# standard types the ADO.NET engine will create for them, so you don’t need to worry about transformations regarding database errors. Of course, you may need to convert them back to your specific types, but it should not be a problem for you Smile | <img src= " align="top" src="http://www.codeproject.com/script/Forums/Images/smiley_smile.gif" /> .

Transactions

As mentioned before, by default, Kerosene will not wrap the execution of any command inside a transaction. This is done by design: even if it would be an interesting feature, it would introduce a critical element beyond the control of your application – and so, following the spirit of simplicity, and the spirit of letting you to specify exactly what you want to do, it is not included. But, no worries, of course you can use transactions, but you have to use them explicitly. Any IKLink instance provides you with the TransactionStart(), TransactionCommit() and TransactionAbort() methods. You can use these methods the way you are expecting to, as in the following example:

link.TransactionStart(); try {
   ... your operations ...
   Link.TransactionCommit();
}
catch { link.TransactionAbort(); throw; }

IKLink provides you with two additional properties. The first one, named TransactionMode, lets you control what kind of transactions you want Kerosene to use. Its value can be either Database, for database transactions, or Scope, if you rather wish to use the TransactionScope mechanism. As far as there is not an active transaction you can set its value at any moment. Its default value is not defined, and it will depend on the specific implementation of your IKLink object.

The second property, named TransactionState, lets you interrogate your IKLink instance and obtain in what transactional state it is: Empty if there is no transaction active, Active if there is a transaction active, or Aborted if the last transaction used was aborted.

The default mechanism implemented by Kerosene also allows you to nest transactions as needed, by using a simple counter mechanism. If this is not enough for your needs, or if you wish to have more deep control on the specifics of the transactions to use, then you can use two mechanisms. The first one is obvious: use the TransactionScope mechanism by yourself. As Kerosene won't initiate any transaction unless you tell it explicitly to do so, you won't have to worry for rogue transactions appearing and dissapearing without your control.

The second one is by using the Connection property of your link object. This property is only available to "direct" link objects, those that are instantiated by using a connection string. In this case, you will need first to be sure the link object is opened, using its IsDbOpened property. If it is not opened, you can use DbOpen() to open it. Then you can use the Connection property to create and manage your own transactions. Finally, you can the DbClose() method to close the connection held by your link object.

Disposing your objects

Basically all objects in Kerosene implement the IDisposable interface. This is because all of them will ultimately refer to an instance of a given IKLink object, and this ones may maintain unmanaged resources. So don’t forget to dispose your commands, and of course, your Link objects, as soon as you are done with them.

Regarding the latter, note that either you can create your Link objects when you are about to use them, or you can use a Singleton pattern, or mix them both. Kerosene is built in such a way that the cost of creating as many Links as you need is not high in most circumstances. But there are scenarios, typically when you are dealing with Maps, where this cost can be noticeable because per each map instantiation there will be a trip to the database. So, you can choose which way fits into your needs.

Finally, note that when disposing a Link its registered objects, as Maps or Transformers, are disposed as well. Keep this in mind if you want to reuse these objects, because you may have to clone them and register them later into another Link before disposing the original one. The good news are that all the interesting classes in Kerosene implement the ICloneable interface (or a variant of it, this kind of deep details will be covered in future articles).

Converting your results

If you are still reading this, by now you are surely used to the fact that, in this basic mode of operation, Kerosene operates conceptually on the rows, or records, as they are returned from the database. By default they are returned as instances of the KRecord class, in order to have a resilient and self-adaptive mechanism able to deal with whatever schema your database may have without the need of external configuration or mapping files.

So far so good, but although this KRecord mechanism is quite powerful and flexible, we can all easily agree in that there are a lot of scenarios where you would like to use your specific business objects instead of the database-related records. Kerosene provides you with two mechanisms to achieve this: the converters and the maps.

Converters in Kerosene

All the enumerable commands permit you to specify a delegate that will be invoked just after the record is returned from the database, but before it is returned to your application. The mission of this delegate is to take the KRecord instance, passed to it as its argument, and to transform it into whatever object you would like to use. There are no limits in what you can do inside the delegate: create a new instance, call again the database, or any other things you want to do.

The easiest way of using this mechanism is by using the ConvertBy(...) extension method as we can see in the following example:

foreach( var obj in cmd.ConvertBy( rec => {
   return new { ((dynamic)rec).Id, Name = string.Format( "{0}, {1}", rec["LastName"], rec["FirstName"] ); }
} ) ) Console.WriteLine( "\n>> Converted = {0}", obj );

In this example we have transformed the record "rec" into a new instance of an anonymous type, accessing the columns using both a dynamic syntax and an indexed one. This new type contains two properties, one named "Id" which is obtained straight from the "Id" column, and a "Name" one that is built using two columns from the record.

Similarly we could have created a new instance of our business class and load it contents using the values stored in the record passed as the argument. You can find more examples in the code included in the download.

Nested readers

As mentioned above, you can do whatever things you want inside your delegates. For instance, you can get more values from the database in each iteration, and use them to build more complex objects. This is called nested readers, and it is precisely what is shown in the next example:

var extCmd = link.From( x => x.Countries ).OrderBy( x => x.Name );
foreach( var ctry in extCmd.ConvertBy( rec => {
   CountryTable table = new CountryTable(); dynamic c = rec; // Handy substitution
   table.Id = c.Id;
   table.Name = c.Name;
   table.RegionId = c.RegionId;

   var innCmd = link.From( x => x.Employees ).Where( x => x.CountryId == table.Id );
   foreach( var emp in innCmd.ConvertBy( rec2 => {
      EmployeeTable tmp = new EmployeeTable(); dynamic e = rec2;
      tmp.Id = e.Id;
      tmp.FirstName = e.FirstName;
      tmp.LastName = e.LastName;
      tmp.CountryId = e.CountryId;

      table.Employees.Add( table );
   } ) ) ;
   innCmd.Dispose();

   return table;
} ) ) Console.WriteLine( "\n>> Country = {0}", ctry );
extCmd.Dispose();

For simplicity I have not included in the example the definitions of the CountryTable class or the EmployeeTable one – but they are pretty straightforward and, anyhow, you can find the complete example in the download at the head of this article.

The overall idea of the example is, firstly, to enumerate through the country records, and transform them into the appropriate instances of the CountryTable class. Then, per each of them, an internal/nested command is executed to find the employee records whose country is the country we are processing, transform them into instances of the EmployeeTable class, and add those instances into the Employees list property of the external country record.

Note that, in order to use nested readers, your database must support this feature, and that your connection string should specify that this support is requested. This is very database dependent, but in my environment the SQL database version I’m using supports it, and I have just had to include a "MultipleActiveResultSets=true" section in my connection string.

Nested readers can be used not only with Query operations, but with the other Insert, Delete, Update and Raw ones as well. In these cases a note of caution apply if you are modifying the key columns as there might be referential integrity rules enforced in your database. Kerosene assumes you know what you are doing, and so it does not try to impede you for doing such operations – but in order them to work you may need to suspend these constraints and to reactivate them later. Again, this is very database dependent, and I encourage you to take a look at the examples provided.

Maps and Entities in Kerosene

The second mechanism Kerosene provides in order to convert your results is the Maps one. Even if its specifics details will be discussed in the article that appears below in the references section, we will see now an introduction.

First thing first: the new Maps mechanism included in this version of Kerosene substitutes completely the former ones, and has become an Entity Framework in itself. But unlike other ORMs, it is completely built to work with POCO classes, not requiring you to modify in any way your business objects, not even with attributes, something that can be quite handy if you don't have access to their source code, as happens often.

How it works? The idea is to create an instance of a "KMap<T>" object, where "T" specifies the type of your business object. Its constructor takes as its first argument the Link this map is associated to, and as its second one the main/primary table where to find the contents for your class, as can be seen in the following example:

var map = new KMap<Region>( link, x => x.Regions );

I have mentioned its "main/primary table" because you can gather contents from more tables if needed. These kinds of customizations are covered in the specific article about Maps that you can find below in the references section.

Note that a given business type can be registered only once per each Link object. To the contrary, as expected, you can have as many different maps as you wish registered in the same Link, as far as they refer to different entity types – something that you have to bear in mind if you are using a Singleton pattern for your Link. But you can refer to the same table as many times as you wish for different maps, and you can register the same business entity as many times as you wish as far as each time it is registered into a different Link instances.

Once you have created your map it tries, by default, to map the columns on the primary table you specified with the properties or fields in your business entity, discarding those that don’t match. You can also customize the columns to fetch from the database, which ones are going to be managed automatically, and which ones are going to be unmanaged by Kerosene and used by your own code to do whatever you want. Also, you can define how the map will behave in each operation, for instance when you want to deal with referential properties. Please refer to already mentioned article below for more details.

Without entering in those details, let’s take a look at the following examples. The first one returns an instance of your business entity of type Region found by using its "Id" column:

Region reg = link.Find<Region>( x => x.Id == "001" );

The Maps mechanism in Kerosene requires that your primary table has at least one key or unique column, as they are used for the Update and Delete operations. But the nice thing is that it does not assume you know ever which columns are the key or unique ones, as it will find them out automatically.

Another difference with the basic mode of operation is that the Maps mechanism in Kerosene does keep track of the instances of your business entities and their states. This is because it needs to know if a given instance has been created straight from your C# code, or fetched from the database, or if it has been modifyied, etc. Let's continue with the above example by adding the following code:

reg.Name = "Europe";
var cmd = link.Update<region>( reg );
reg = cmd.Execute();
</region>

In this case Kerosene will find automatically that you have modified your instance and, when you invoke the Update(...) method, it will update only the corresponding column (or columns) for precissely the original instance. If there are no modifications the command returns without even accessing the database, so saving one trip to it.

Also, Kerosene will assure that the instances your are using refer internally to the same object, so avoiding duplicate or not-up-to-date instances. There is a specific example about this in the download if you want to take a look at it.

Unlike the previous Find case, the other commands are not executed by its instantiation: they create an instance of an object that require you to invoke its Execute() method. The reason for this is because Find is just an special case, provided for your convenience. But the more generic Query, Insert, Update or Delete ones must provide you a way to obtain the SQL code they will execute (by their corresponding ToString() method), for instance for logging or debugging purposes.

The Maps mechanism is designed to let you focus on your business entities, forgetting all the entire database related details (*). The idea is that you will create your entities in C# code the way you are used to it, or fecth them from the database in a handy way, manipulate them in any way you wish, and, when the time comes, let Kerosene to deal with the persistence part. Let me prove my point with the following example:

Region r500 = new Region() { Id = "500" };
Region r510 = new Region() { Id = "510", Parent = r500 }; r500.Childs.Add( r510 );
Region r511 = new Region() { Id = "511", Parent = r510 }; r510.Childs.Add( r511 );
Region r512 = new Region() { Id = "512", Parent = r510 }; r510.Childs.Add( r512 );

r510 = link.Insert( r510 ).Execute();

In this example we have created a set of entities using their C# constructor, and set their properties afterwards. What makes this example quite interesting is that they have a list property that is used to implement a parent-children relationship, and we have honored it by using only C# code, without any references to the database.

It is even more interesting when you realize that the instance we have inserted into the database is not even the root of the hierarchy, but rather one of its children. Yes, it doesn't matter: with some easy customizations (*) the complete hierarchy is persisted in the database as expected.

(*): As you can expect there is a limit in the magic Kerosene can do. These advanced capabilities require you to tell Kerosene how to deal with dependent or parent instances in the database. But the good news are that this is achieved by writing, and only once, a few delegates. We are not going to go deeper into the Maps mechanism in this introductory article: all the details can be found in the article below in the references section.

Dynamic Syntax

Still with me? Good, because we are now going to investigate a bit further the dynamic syntax Kerosene supports.

We have seen before many examples of this dynamic syntax. It is based in the usage of dynamic lambda expressions that, when parsed, generates the appropriate SQL code your database can understand. This mechanism permits Kerosene to intercept, in a flexible and no-compromises way, the expressions you have written, and to manipulate them accordingly.

Dynamic Extension Methods

A "dynamic extension method" is defined as a virtual method call attached to a dynamic argument, or to a property or method of a dynamic argument. In purity they don’t exist, they are just placeholders for the parser so that it to intercept them and include in the SQL code the appropriate sequence.

We have seen already some examples: the "As(...)", "And(...)" and "Or(...)" methods, which come by default. But there are many more depending upon the specific version of your database. For instance, the Microsoft SQL version included in the download comes with more extension methods adapted to it. Let’s see the next example:

var cmd = link
  .From( x => x.Employees.As( x.Emp ) )
  .Where( x => x.Emp.BirthDate.Year() >= 1970 );

This will produce the following SQL code:

SELECT * FROM Employees AS Emp WHERE ( DATEPART( YEAR, Emp.BirthDate ) >= @p0 )

The "Year()" dynamic extension method, when applied to a column name, will interpret that this column is of the SQL type "date", and that you want to extract from it its year value using the SQL's "DATEPART" function. As you can imagine, the parsing engine we have used (the one for Microsoft SQL databases) also supports the "Month()", "Day()", "Hour()", "Minute()", "Second()", "Millisecond()" and "Offset()" ones to use with "date" or "time" types.

It also supports other dynamic extension methods for other SQL types, as: "Left()" and "Right()", "Len()", "Lower()" and "Upper()", "Like()", "NotLike()", "Contains()", "PatIndex()" and "SubString()", "LTrim()", "RTrim()" and "Trim()", and "Cast()", with the usage and number of arguments you can expect (sorry, this article is becoming very long and I have not space here to provide an example for all of them).

Other parsing engines may choose the methods they would like to intercept depending upon the specific functions the database supports. See below the introductory chapter on how to extend Kerosene for more information.

One important note: by design Kerosene does not even try to intercept any semantic errors in the SQL code you have written. The reason for this is that this interception should be done by the piece who really knows what is acceptable or not, your database engine, instead of reinventing the wheel and trying to keep in sync with whatever changes may happen in the future. So, do expect to receive some errors from the database if you write bad SQL code.

Extending dynamically the Kerosene’s syntax

The above is also very relevant because Kerosene has a built-in dynamic extensibility mechanism that permits you to code SQL functions even if they are not known in advance to the parsing engine.

When it finds something that it has been not instructed to intercept, it assumes that you know what you are doing, and makes its best to translate it into SQL code. Let’s see for instance the next example:

var cmd = link.From( x => x.Employees ).Select( x => x.Count( x.Id ).As( x.SumOfEmployees ) );

In this example the "Count" function is not known to Kerosene. As it is attached straight to a dynamic argument it is assumed that it is a global SQL function, and so it does not take its first argument from the column at his left (which was the convention used with the As, And and Or extension methods above). Then, its name is used to code the function to invoke, and its parameters are parsed as appropriate, generating the following SQL code:

SELECT Count( Id ) AS SumOfEmployees FROM Employees

A final side note: please note that we had to use an alias in this command. The reason is that, even if Kerosene can use a default table name when needed, it cannot use anonymous columns: every column must have a name. In this case the result is returned in the form of an anonymous column, which will lead to an exception. So the easiest way to solve this situation is by setting and alias on the result, as we did with the "SumOfEmployees" name.

Escaping from the parsing engine

From the very beginning of this project it was clear to me that it would be impossible to cope with all possible combinations of database syntaxes, all the future changes in their versions, and so on. So I decided to include a mechanism that will let you include any arbitrary text in your lambda expressions, including raw text if you wish so.

This mechanism is activated any time you write a "direct invocation of your dynamic argument": in this case, the parsing engine will merely concatenate all its argument with spaces and not with commas, and also, if it finds an argument that is a string, it is not parameterized. A "direct invocation" is any expression (or part of it) with the form "x => x( ... )".

The clearest example is the GroupBy statement of the Query/Select commands. In former versions of the library there were a number of methods that qualify this statement further, that were also dependent on the specific database vendor and version. Those methods have been deprecated because with the new mechanism are no longer needed. For instance:

var cmd = link ... GroupBy( x => x( x.CountryId, "HAVING", x.CountryId >= "us" ) );

gets translated into:

... GROUP BY CountryId HAVING ( CountryId => @p0 )

It is interesting to note that the "HAVING" argument is not parameterized as it is a string argument, whereas the "us" string has been parameterized as it is part of a bigger expression. The first and third arguments are parsed as usual as they are not strings. Finally, the three of them are merely concatenated with spaces.

A bit of theory on Commands

It is time now to go a bit deeper in what a command is, the different command types supported, and how to use them.

A command is a specialized object that implements the IKCommand interface, and whose mission is to annotate all the details it may need to generate the corresponding SQL code. There are Query commands, Insert, Delete and Update ones, and, finally, Raw commands that permits you use any text (for instance to execute stored procedures).

All commands have a Link property that refers to the IKLink object they were instantiated for. Each link has a Factory property that refers to the factory specifically adapted for the vendor and version of your database. This factory exposes two important methods: the "CreateParser()" one, whose mission is to instantiate the parser to use when parsing the dynamic expressions, and the "CreateParameterList()" one, whose mission is to create, for this command, the repository where to store the parameters extracted from the expression you wrote.

Your link can also be a "direct" one, meaning that it knows how to connect directly to your database (by virtue of holding a connection string in its ConnectionString property). In this case, the factory should implement the IKFactoryDirect interface, whose DbProviderFactory property returns the ADO.NET provider factory to use.

A final note: in previous versions there was the need to create specific classes for the commands to use with each specific database vendor and version. With the new architecture of Kerosene, along with the escape mechanism mentioned above, we can now live with just the generic versions of the commands, and thus considerably simplifying the overall solution.

Query/Select commands

A Query command is a specialized object for generating SELECT commands against your database. You can instantiate them using its constructor, or by using some extension methods of the link objects: the "Query()" one, that basically mimics the constructor (but permits a handy chaining of method invocations), and several overrides of the "From()" one.

We have used the first override when we have instantiated a query command as in "link.From( x => x.Employees )", where its parameter shall be the table to use. Remember that we can attach an "As(...)" dynamic extension method to it if we want to add an alias. And you can use several table specifications if you wish by separating them with commas.

The second form just takes a string as the specification of the table. It is handy if you want to store the table in a string variable, and internally is also used for some specialized operations. The only caveat is that in this case, if you want to use an alias, you have to write it explicitly, like in this example:

var cmd = link.From( "Employees AS Emp" );

Now, the third form is the one we can use to include nested commands. It takes as its first argument a command object, and as its second one a mandatory alias to assign to it. As an example let’s see the following code:

var cmd = link
    .From(
        link.From( x => x.Countries.As( x.Ctry ) ).Where( x => x.Ctry.Id == "us" ),
        x => x.Location
    )
    .From( x => x.Employees.As( x.Emp ) )
    .Where( x => x.Emp.CountryId == x.Location.Id )
    .Select( x => x.Emp.All() );

When executed it will produce the following SQL code:

SELECT Emp.* FROM ( SELECT * FROM Countries AS Ctry WHERE ( Ctry.Id = @p0 ) ) AS Location, Employees AS Emp WHERE ( Emp.CountryId = Location.Id )

Chaining methods

As mentioned before there is no limit to the number of times you use a given method or in what order you chain them. Kerosene reorders your expressions to comply with the SQL syntax. For instance, let’s assume you want to find all the employees that belong to the "Europe, Middle East, and Africa" super region. One possible way to achieve that is as follows:

var cmd = link
   .From( x => x.Employees.As( x.Emp ) ).Where( x => x.Emp.JoinDate >= new CalendarDate( 2000, 1, 1 ) )
   .From( x => x.Countries.As( x.Ctry ) ).Where( x => x.Ctry.Id == x.Emp.CountryId )
   .Select( x => x.Ctry.All() )
   .Select( x => x.Emp.Id, x => x.Emp.BirthDate, x => x.Emp.LastName );

This will produce the following SQL code:

SELECT Ctry.*, Emp.Id, Emp.BirthDate, Emp.LastName FROM Employees AS Emp, Countries AS Ctry WHERE (( Emp.JoinDate >= @p0 ) AND ( Ctry.Id = Emp.CountryId ))

Disclaimer: I am not, in any way, presuming this is the most effective SQL command. I am just trying to provide different examples on how to use the capabilities of Kerosene, and how these capabilities are designed to accommodate to your way of thinking.

Where-In and Where-Equals

The "IN" and EQUALS ("=") syntaxes are also allowed. As an example, let's assume now that you want to find all the employees that, in this case, do not belong to the "Europe, Middle East, and Africa" super region. One possible way to achieve it is as follows:

var cmd = link
  .From( x => x.Employees ).Where( x => !x.CountryId.In(
    link.From( y => y.Countries ).Select( y => y.Id ).Where( y => y.RegionId.In(
      link.From( z => z.Regions ).Select( z => z.Id ).Where( z => z.ParentId =
        link.From( p => p.Regions ).Select( p => p.Id )
          .Where( p => p.Name == "Europe, Middle East & Africa" )
) ) ) ) );

This produces the following SQL code:

SELECT * FROM Employees WHERE ( NOT CountryId IN ( SELECT Id FROM Countries WHERE RegionId IN ( SELECT Id FROM Regions WHERE ParentId = ( SELECT Id FROM Regions WHERE ( Name = @p0 ) ) ) ) )

The "x => x.Member.In( Expression )" construction is the entry for the "IN" syntax, translating it into the "Member IN ( Expression )" SQL statement. The same logic applies when using the assignment operator "=", so translating "x => x.Member = expression" into "Member = ( Expression )", or "x => x.Member != expression" into "NOT Member = ( Expression )" (note that this second example uses the "!" negation operator).

Joins

Another way to get the results we intended to get in the last examples is by using joins. The "Join()" method has two overrides: the easy one just takes a dynamic expression that states the contents of your JOIN clause. The second one takes an additional first argument that is a string with the specific JOIN type (you can use just "JOIN", or "LEFT JOIN", or "INNER JOIN" ... or any variant you may need).

So, for instance, you can use the following code for finding the same records as in the examples above:

var cmd = link
   .From( x => x.Employees.As( x.Emp ) )
   .Join( x => x.Countries.As( x.Ctry ).On( x.Ctry.Id == x.Emp.CountryId ) )
   .Join( x => x.Regions.As( x.Reg ).On( x.Reg.Id == x.Ctry.RegionId ) )
   .Join( x => x.Regions.As( x.Super ).On( x.Super.Id == x.Reg.ParentId ) )
   .Where( x => x.Super.Name == "Europe, Middle East & Africa" )
   .Select( x => x.Emp.All() )
   .Select( x => x.Reg.All() )
   .OrderBy( x => x.Reg.Id ).OrderBy( x => x.Emp.Id );

This command will produce the following SQL statement:

SELECT Emp.*, Reg.* FROM Employees AS Emp JOIN Countries AS Ctry ON (( Ctry.Id = Emp.CountryId )) JOIN Regions AS Reg ON (( Reg.Id = Ctry.RegionId )) JOIN Regions AS Super ON (( Super.Id = Reg.ParentId )) WHERE ( Super.Name = @p0 ) ORDER BY Reg.Id ASC, Emp.Id ASC

Note how we have used the "As(...)" and "On(...)" dynamic extension methods to specify an alias of the table to join, and the join condition. Indeed, if you use no "On(...)" method an exception will be raised.

Top

You can include a TOP clause by using the "Top()" method. It takes an int that specifies the number of records the database will produce. Nothing else to say here.

Order By

The "OrderBy()" method is used to annotate the contents for the ORDER BY clause. Its first argument is a lambda expression that specifies the column to use to sort the results. Its second argument is a boolean one, being true by default to specify that the sort order should be "ascending", or false to specify it should be "descending". For instance:

var cmd = link.From( ... ) ... OrderBy( x => x.Id, ascending: false );

Group By

The "GroupBy()" method is used in Query commands to annotate the contents of the GROUP BY clause. It takes a variable list of lambda expressions, each of them being the specification of a given column. Please see the discussion above about escaping from the parsing engine, where this clause has been used as an example.

Raw Commands and Stored Procedures

Kerosene does also provide a way to send "raw" commands to the database, without losing the ability of receiving the results and treat them dynamically, as we have done so far. A "raw" command is one where you explicitly take care of writing the SQL text to execute. You can instantiate them either by using their constructor, or by using the "Raw()" extension method of the IKLink objects.

Raw commands provide two major methods. The first one is "Set(...)", that sets the contents of the command, both its text and its parameters. You can use a dynamic lambda expression, or you can use a second override that takes a string literal and a variable list of objects to specify the command’s parameters. In this latter form, note that you can use the standard C# escape format sequence (as in "{0}") to specify those parameters in your text. Let’s see an example:

var cmd = link.Raw( "SELECT * FROM Employees WHERE BirthDate >= {0}", new CalendarDate( 1969, 1, 1 ) );

The second one is "Append(...)" that, as its name implies, appends the contents specified to the contents the command already has. For instance:

cmd.Append( " AND Id >= {0}", "007" );

Note that the index in the format escape sequence refers to the method you are using. Internally Kerosene will assign to it the proper parameter name avoiding name collisions.

One interesting thing to mention is that, when you try to enumerate on any raw command except SELECT related ones, you have to explicitly write the command to include this enumeration capability. For instance, to insert a record and return it you may want to write something like this:

var cmd = link.Raw(
   "INSERT INTO Employees ( Id, FirstName, LastName, CountryId, BirthDate, JoinDate, Photo )"
   + " OUTPUT INSERTED.*" // Needed!!!
   + " VALUES ( {0}, {1}, {2}, {3}, {4}, {5}, {6} )",
   "007", "James", "Bond", "uk", new CalendarDate( 1969, 1, 1 ), null, new byte[] { 0, 0, 7 } );

Where the "OUTPUT INSERTED.*" sentence applies to Update and Insert operations. You can use "OUTPUT DELETED.*" for Delete operations. And, of course, your specific database may need different statements.

Another handy use of Raw commands is that they permit you to use stored procedures. For instance let’s see the next example:

var cmd = link.Raw(
   "EXEC employee_insert @FirstName = {0}, @LastName = {1}",
   "James", "Bond" );

In this example we invoke the stored procedure named "employee_insert" using two parameters. The convention names of those are very database dependent, but Kerosene imposes no restrictions to them.

Update commands

The Update commands are, again, instantiated either by their constructor or by the "Update()" extension method of the IKLink objects. Their TableName property will contain the table being updated, and they provide two specific methods:

  • The "Where(...)" method, used to locate the record to update (or records if the operation can apply to many of them).
  • The "Column(...)" method, used to specify the columns to update and how, by using an assignation syntax as in "x => x.Name = "New Name"". You can use as many specifications as needed separating them by commas, or chain as many "Column(...)" methods as you wish. Let’s see the next example:
var cmd = link.Update( x => x.Employees )
   .Where( x => x.FirstName >= "E" )
   .Column(
      x => x.ManagerId = null,
      x => x.LastName = x.LastName + "_1",
      x => x.Photo = new byte[] { 99, 98, 97, 96 }
);

Here we are modifying all the records whose first name is bigger or equal than "E" setting their ManagerId column to null, appending to its LastName column a "_1" string, and setting its Photo column to an array of bytes. Note that Kerosene imposes no limits to the SQL code you want to write: this is why, for instance, we were able to append such string to the LastName column using its previous contents as a seed.

Insert commands

The Insert commands are very similar to the Update ones, except that they do not have a "Where()" method. As expected they are instantiated either by using their constructor, or by using the "Insert()" extension method.

Delete commands

Similarly, the Delete commands are quite similar as the Update ones, except that they only have the "Where()" extension method, and that they are instantiated either by its constructor or by the "Delete()" extension method.

Notice that, if you don’t use any WHERE clause, when executed they will delete all the records in the table they refer to. Kerosene does not try to impede it as it might be precisely what you wanted.

Extending Kerosene

By popular demand I am including here a small introduction on how to extend Kerosene. You can find more details in the article that appears below in the references section.

Kerosene is architected to be agnostic about what specific flavor of database you are dealing with. The overall idea is to consolidate all the customizations needed in order to support another database in a very limited set of elements, where also the number of methods to override is very few.

The first element is the IKFactory interface. It specifies the core factory methods that should be adapted to the specific database you are implementing. The download comes with two classes: KFactorySQL and KFactoryDirectSQL that, as their names imply, are built to provide the functionality needed for Microsoft SQL Server databases. The second one is designed specifically for when you are connecting to a SQL database directly, instead of when you are going throw the client-server WCF version, for instance. Take a look at them because they show that in order to implement your own database you only have to override three very simple methods.

The second one is the KParser class, whose mission is to take a "dynamic lambda expression" and to translate it into something that your database can understand. These parsers are instantiated by the "CreateParser()" factory method of the IKFactory interface. The internal parsing mechanism is quite complex, but the good news are that, in most scenarios, you have to override just one method in order to provide support to your own database. This is precisely what I have done with the specialized KParserSQL class in order to have support for Microsoft SQL databases.

And the third one is our, by now, old friend the IKLink interface. As mentioned before, the classes that implement this interface are in charge of maintaining all the details needed to access the database, to open and close the connections to it when needed, the access to the transactional mechanism, and some factory helper methods to create the enumerators and executors for your commands. Again, it is very easy to build support for your own database because in most scenarios you have to override just two factory methods.

WCF scenarios

Another specialized link version included in the download is the one designed to access the database through a WCF server. In this case, instead of establishing a direct connection to the database you will connect to a WCF service that, on your behalf, will connect to the real database it is configured to do so. This is handy in, for instance, remote scenarios where you don’t want to expose your database to the Internet.

What is even more interesting in this version is that, instead of using a connection string, you will rather use a string specifying the endpoint where to find the WCF service. This server will create a link to an underlying database without the client knowing the details of it (well, except of its flavor as it is needed to parse the commands). The net effect is that you don’t have to expose externally any knowledge about the host running your database, what protocols it is using, etc.

Again, I’m not going to cover its details in this introductory article, but instead you can refer to its specific article as it appears in the references section below.

References

  • [1] DynamicParser. Describes how to use C# dynamics to convert a dynamic lambda expression in the form of a delegate into an expression tree: the functionality that is at the core of the Kerosene's parsing and translating capabilities. It can be found here: DynamicParser.
  • [2] Kerosene Maps. Describes how to extend the Kerosene library to become an entity framework specifically built to provide complete support for POCO classes. It can be found here: KeroseneMaps.
  • [3] Kerosene for WCF. Describes how to use Kerosene in WCF scenarios. It can be found here: Kerosene for WCF.
  • [4] Kerosene Internals and How to Extend Kerosene. Work in progress.

History

  • [v5, September 2012]: Kerosene is the fifth version of this project. Its main topics are: better performance, an improved dynamic parsing mechanism, a cleaner solution’s architecture, a completely maps mechanism that has become an entity framework, and a number of other improvements.
  • [v4.5, May 2011]: this was a maintenance version that allowed KyneticORM to use any arbitrary type for the parameters of a command, with a transformer mechanism that converts them to instances of objects understandable by the ADO engine. It avoided the need of converting them to strings.
  • [v4, January 2011]: this version added support for serialization and WCF scenarios, an improved support for transactions, and corrected some minor bugs.
  • [v3, October 2010]: KyneticORM was the third version of the project. It was focused on an improved parsing mechanism and performance.
  • [v2, August 2010]: MetaDB was the second version of this project. Its focus was to include some improvements and to resolve some bugs.
  • [v1, June 2010]: MetaQuery was the first version of this project. It focus was basically to send queries against a MS-SQL database, with no support of maps, and very primitive Insert, Delete and Update operations.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Moises Barba

Spain Spain
Moises Barba has worked as CIO and CTO for some start-ups, and as Consulting Director for some major multinational IT companies. Solving complex puzzles and getting out of them business value has ever been among his main interests - that's why he has spent his latest 20 years trying to combine his degree in Theoretical Physics and his System Engineer MCSE with his MBA... and he is still trying to figure out how all these things can fit together. Even if flying a lot across many countries, along with the long working days that are customary in IT management and Consultancy, he can say that, after all, he lives in Spain (at least the weekends).
Follow on   Twitter   LinkedIn

Comments and Discussions

Discussions on this specific version of this article. Add your comments on how to improve this article here. These comments will not be visible on the final published version of this article.
 
-- There are no messages in this forum --

Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
QuestionUsing library from Visual Basic PingroupPaul_Williams25-Mar-14 10:06 
AnswerRe: Using library from Visual Basic PinmemberMoises Barba25-Mar-14 23:05 
GeneralGreat aticle but download corrupt PinmemberWebVize19-Mar-14 23:38 
GeneralRe: Great aticle but download corrupt PinmemberMoises Barba19-Mar-14 23:56 
GeneralRe: Great aticle but download corrupt PinmemberWebVize20-Mar-14 0:04 
GeneralRe: Great aticle but download corrupt PinmemberMoises Barba20-Mar-14 0:10 
GeneralRe: Great aticle but download corrupt PinmemberWebVize27-Mar-14 1:30 
GeneralRe: Great aticle but download corrupt PinmemberMoises Barba27-Mar-14 1:49 
GeneralRe: Great aticle but download corrupt PinmemberWebVize27-Mar-14 2:19 
QuestionAny performance comparison? PinmemberJ4Nch11-Mar-14 4:23 
AnswerRe: Any performance comparison? PinmemberMoises Barba11-Mar-14 4:27 
QuestionMy Vote of 5 PinmemberMike DiRenzo6-Mar-14 4:22 
AnswerRe: My Vote of 5 PinmemberMoises Barba6-Mar-14 4:36 
AnswerRe: My Vote of 5 PinmemberMoises Barba11-Mar-14 4:05 
GeneralRe: My Vote of 5 PinmemberMike DiRenzo11-Mar-14 22:49 
GeneralRe: My Vote of 5 PinmemberMoises Barba12-Mar-14 0:06 
QuestionUse Kerosene ORM with Access Database PinmemberMember 867745821-Sep-13 5:39 
AnswerRe: Use Kerosene ORM with Access Database PinmemberMoises Barba21-Sep-13 11:37 
GeneralRe: Use Kerosene ORM with Access Database PinmemberJason Law23-Oct-13 17:01 
GeneralRe: Use Kerosene ORM with Access Database PinmemberMoises Barba23-Oct-13 23:36 
GeneralRe: Use Kerosene ORM with Access Database PinmemberJason Law30-Dec-13 22:05 
GeneralRe: Use Kerosene ORM with Access Database PinmemberMoises Barba31-Dec-13 3:51 
GeneralRe: Use Kerosene ORM with Access Database PinmemberJason Law1-Jan-14 15:37 
GeneralRe: Use Kerosene ORM with Access Database PinmemberMoises Barba1-Jan-14 23:29 
GeneralRe: Use Kerosene ORM with Access Database PinmemberJason Law5-Jan-14 22:25 
GeneralRe: Use Kerosene ORM with Access Database PinmemberMoises Barba7-Jan-14 6:04 
GeneralMy vote of 5 Pinmemberbaxiqiuxing17-May-13 0:35 
GeneralRe: My vote of 5 PinmemberMoises Barba20-May-13 10:57 
QuestionGreat article PinmembercptKoala1-May-13 10:48 
AnswerRe: Great article PinmemberMoises Barba1-May-13 11:42 
GeneralMy vote of 5 PinmemberPaulo Zemek19-Apr-13 8:47 
GeneralRe: My vote of 5 PinmemberMoises Barba19-Apr-13 9:54 
GeneralRe: My vote of 5 PinmemberMoises Barba11-Mar-14 4:29 
QuestionAND and OR PinmemberPaulo Zemek19-Apr-13 8:03 
AnswerRe: AND and OR PinmemberMoises Barba19-Apr-13 8:42 
GeneralRe: AND and OR PinmemberPaulo Zemek19-Apr-13 8:46 
GeneralRe: AND and OR PinmemberMoises Barba19-Apr-13 9:53 
QuestionExcellent article PinmvpEspen Harlinn20-Mar-13 9:40 
AnswerRe: Excellent article PinmemberMoises Barba20-Mar-13 10:01 
AnswerRe: Excellent article PinmemberMoises Barba11-Mar-14 4:30 
QuestionDataBinding PinmemberLeon_pro18-Mar-13 23:57 
AnswerRe: DataBinding PinmemberMoises Barba20-Mar-13 10:24 
QuestionDatabinding? Pinmemberjamesklett1-Feb-13 8:44 
AnswerRe: Databinding? PinmemberMoises Barba20-Mar-13 10:24 
GeneralRe: Databinding? PinmemberLeon_pro20-Mar-13 10:53 
GeneralRe: Databinding? PinmemberMoises Barba20-Mar-13 10:59 
GeneralRe: Databinding? PinmemberLeon_pro21-Mar-13 7:02 
GeneralRe: Databinding? PinmemberMoises Barba21-Mar-13 8:47 
GeneralRe: Databinding? PinmemberLeon_pro21-Mar-13 9:09 
Questionsome questions PinmemberCasey.nnn23-Jan-13 14:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 6 Sep 2012
Article Copyright 2010 by Moises Barba
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid