Click here to Skip to main content
Click here to Skip to main content

Kerosene ORM Maps In Depth

, 11 Mar 2014
Rate this:
Please Sign up or sign in to vote.
A deep dive tutorial on Kerosene ORM Entity Maps, extending its capabilities to provide a no-compromises Entity Framework for POCO entities

Introduction

This article provides a deep dive tutorial on the Kerosene ORM's "Entity Maps" operational mode: how to use it and its internals. If you have not done it already, you really want to read the Kerosene ORM Introductory Article, because, otherwise, you will feel lost. Also, this article comes with no specific download: it assumes you are using the one provided by that introductory article, where you can find all the samples and library packages.

Motivation

Let me recall that, in its "Dynamic Records" operational mode, Kerosene ORM works with records, instances of dynamic objects that implement the 'IKRecord' interface. While these instances adapt themselves to whatever structure the results produced by the execution of a command may have, converting them into instances of your business classes involve writing a specific converter per each command.

Yeah, ok, sure, you can always write these delegates once and use them in any commands that may need them. And indeed this approach suffices in many scenarios - actually it very well might be the recommended approach if you do not have a receiving class, or if you have a data intensive scenario where the conversion penalty into a business class by using a map might not be acceptable. But by doing so you cannot use concepts like 'navigational' or 'dependency' properties, and you can not keep track on the state of your business entities (whether they have been updated, or are in and insert pending state, and so forth).

So that's why, among other reasons, Kerosene ORM supports an "Entity Maps" mechanism (that, by the way, is the analogous to the MS' Entity Framework or to the nHibernate one). It has complete and unconditional support for POCO classes, not requiring to modify them in any way, not even having to pollute them with ORM-related attributes, or to write any external mapping or configuration files. Also, of course, you don't have you derive your classes from any ORM specific base one.

Entity Maps Modes

In its default 'Table Maps' mode Kerosene ORM does everything for you. Indeed, in the easiest case you just need to tell your connection that you are interested in this and that POCO types, and that's it. Interesting enough, this mode is used very often, so many times you find yourself just writing your POCO classes, and registering them into your link (connection) instance!

If for whatever circumstances this default mode is not enough for your needs, no worries, Kerosene ORM is extremely flexible and can be configured and customized to almost any extend you can imagine. We can specify what columns are to be considered in a given map, what aren't, what members in your POCO class should not have to be taken into consideration, and so forth. And, of course, all of this without having to "touch" in any way these POCO classes.

Apart from the 'Table Maps' mode, as soon as we want to use navigational or dependency properties, we are entering into the land of the 'Lazy Maps' or 'Eager Maps' modes. Their names reflect whether to load these properties either in advance ('Eager' mode) or only when their getters are used ('Lazy Mode'). The former is used when these properties are not virtual, whereas the later is used when we can mark these properties as virtual ones. We can mix both modes as we wish or need without any problems.

In previous versions the 'Lazy' mode outperformed the 'Eager' one by more than a 4:1 factor in terms of pure performance. In this current version the difference is almost neglectable for small tables, even if it is still something to be taken into consideration for tables with more than a couple of thousand records.

The reason is because the 'Eager Mode' may end up pre-loading huge object graphs, depending upon how complex and deep your dependencies are, and this may have a noticeable effect both in terms of memory consumption and in delays while that graph is being obtained from the database. On the flip side, once that graph is loaded, access to the content of these properties is done at 'getter speed'.

'Lazy Maps', on the contrary, do not load that contents along with the record. They implement specialized getters and setters so that they are fetched only when needed. You have not to do anything to use lazy maps but mark your desired properties as virtual, everything else is done behind the scenes by Kerosene ORM.

What is a Map?

A map is an object that serves as the linkage between a given primary table in your database and a given POCO class in your application. As mentioned above, a map must be registered into a link, specifying the primary table where to find the main contents of your entities (even if more tables can be involved, as we will see later). A given type can only be registered once in a given link, but as many times as you want in different ones. This way you can have different connections (or data contexts), each having its own map definitions if needed.

Note that Kerosene ORM handles type registration in a very strict fashion. A type derived from a registered one is considered a completely different type from the registration mechanism perspective: no inheritance, covariance or contravariance are taken into consideration. This way we can register maps for a hierarchy of related types that may share the same table, without entering into problems of type inheritance.

Maps implement the generic 'IKMetaMap' interface. Among many other things it provides the 'EntityType' property, that holds the POCO type the map is associated with, the 'MetaLink' one, that holds the link the map is registered into, and the 'Table' one, that holds the name of the primary table where to find the contents of the entities managed by this map.

Note that typically you are not going to use this interface, but rather instances of the 'KMetaMap<T>' class, where 'T' is the type of the entities managed by the map. The reasons of being for the un-typed interface are to provide support for lists of maps, regardless the type each one is associated with, and to support access to a number of operations regardless the concrete type of the entities managed by that map.

When a map is created it is in a not yet in a validated state (which is maintained in its 'IsValidated' property). While it is not validated its structure and configuration can be customized as needed. Once it is validated any attempt to modify it will throw an exception. We don't need to validate a map explicitly, it happens automatically as soon as the map is used for any entity-related purposes or operations. But if, for whatever reason, you want to do so, its 'Validate()' method can be invoked by your application.

You may recall that a link can be cloned. This capability is provided in order to create a different data context from an existing one, that's used as a template, keeping all customizations done on the original link. For your convenience, when a link is cloned it also clones any maps registered into it, but leaving the new cloned ones in a non-validated status. This way you do not have to bother to register the types and creating new maps again, while being able to customize these new maps as you wish.

We will see the other properties and methods of the map instances in the explanations below.

Requirements on your POCO classes

  • The only requirement Kerosene ORM imposes is that your business entities are represented by reference types, classes, not structs. Since this current 6.x version series the former requirement that these types have a parameter-less constructor is even removed. It helps, though, if they have one, even if it is private, but this is really not a strong requirement any longer.

And that's it! Kerosene ORM does not require you to pollute your POCO classes with any ORM-related stuff, not even attributes. You don't have to write and maintain any mapping or configuration files. You don't have to use any code generation tools that only God knows what they are producing. And, no, you don't have to derive your classes from any ORM-specific one.

The Scenario

To get out the most of the following discussions and examples, we are going to use the same business scenario as the one we used when discussing the "Dynamic Records" operational mode. Let me recall that it is just a sample (an minimalist) HR system composed by three tables, as follows:

DB diagram

  • Remember that the three of them have an 'Id' column - but that this happened completely by chance! Kerosene ORM does not require you to use any given magic name, nor it forces you to follow any convention, or to configure the names to use for the primary key column (or columns) or the identity column (or columns).
  • Actually you don't even have to know what would be their names and types. The only real requirement is that there should be a way by which the records in the database can be unequivocally identified. Whether this is achieved by using primary columns, or unique valued ones, it doesn't matter. What's more, Kerosene ORM does not even require your POCO classes to have members to hold this information, as it can be stored in the metadata package the library associates with each entity at run time. This scenario is really quite an advanced and border case one, but sometimes it may be needed and it is an unique feature of Kerosene ORM. We will see in the "Internals" section below more details about it.
  • To make things a bit more interesting there are a number of references and self references among the tables: each employee can be associated with its manager through its 'ManagerId' column, which can be null if it has no manager, and also it must be associated with a given country through its 'CountryId' column; each country must be associated with a given region through its 'RegionId'; and finally each region can be associated with its parent region through its 'ParentId' column that can be null if that region is a top-most one.
  • Finally note that the above diagram just represents our current knowledge on the structure of our database. It might not be a complete description of it and, in the general case, it won't be. Also note that we just need to have a high level understanding of the types of the columns, as Kerosene ORM will handle the appropriate type conversions for us.

Table Maps

The default mode used by Kerosene ORM is known as the 'Table Maps' one. This mode is architected to serve as an almost-direct-map among the columns in your tables and the corresponding members in your POCO classes.

Common Map Operations

Before entering in how to customize our maps let's firstly discuss how can we use them.

Map Registration

In this mode all that you need to do, in the general case, is to register into your link instance the POCO type you are interested at, just by creating a new map instance with the type of the entities, the link, and the name of the primary table:

new KMetaMap<Employee>(link, x => x.Employees);

We don't even have to assign the newly created instance to a variable, or to keep track of it, as these things are done automatically by Kerosene ORM on our behalf.

To prevent re-register the same type again into the link we can use the link's 'Map<T>()' extension method, that returns the registered map for the given type, or null if no such map can be found in the link:

if(link.Map<Employee>() == null)
   new KMetaMap<Employee>(link, x => x.Employees);

Remember that Kerosene ORM handles entity types in a strict fashion. So, if for instance, we have a 'Manager' class that inherits from the 'Employee' one, the map that would have eventually been registered for that 'Manager' type is not returned by the above example.

Map Validation

When the time comes to use this map for any entity related purpose Kerosene ORM will automatically validate the map. Behind the scenes the library will find the available columns in the primary table specified for the map, and then will try to match the names of these columns against corresponding members in your POCO class. Whether this match is done in a case sensitive fashion or not depends on the 'CaseSensitive' property of the 'engine' instance the link of this map is associated with.

Remember that, in the general case, you don't have to have complete knowledge about the structure of your databases. So it may very well happen that your POCO class only contains a partial set of the available columns. In this case, the unmatched columns are said to be 'discarded' and, for performance reasons, won't be taken into consideration any longer. The list of discarded columns can be found in the 'DiscardedColumns' property of the map once it is validated.

The recommended way of validating a map is to let Kerosene ORM to initiate this operation when needed, as we have already discussed. Remember also that you have the 'Validate()' method available if you wish to perform this operation when you want it to be done. It might be useful when, for instance, you have created your map and then you want to lock it, so that it won't accept any further customizations.

Querying an Entity

We have used so far just one line of code to register our map, but this is enough to let us now query the database, and to obtain our entities back. Really, in many scenarios, this is just all what we need to do. Are you lazy enough to appreciate this beauty?

Well, jokes apart, the idea now is to create a mapped query command, customize the query, and finally execute it to obtain the entities we are interested at. When using maps there are a number of extension methods on the link instances that we can use to instantiate commands; in our case we can use the 'Query<T>()' and the 'Where<T>()' ones. The first one creates an empty query, whereas the second one allows us to specify the query conditions in the same shot.

Let's see an example. Let's suppose we are interested in obtaining the top-most region. One way of doing so, based upon our limited knowledge of our database, is as follows:

var cmd = link.Where<Region>(x => x.ParentId == null);
var reg = cmd.First();

Among the nicest things in Kerosene ORM is the fact that it permits you to express any logic in your queries, without being constraint by what members your POCO class may have or not. So in the above example the 'ParentId' member may not even have to be mapped into a member of the 'Region' class, or it may not even exist on it.

What's more, in advance scenarios you can use mapped queries that involve other tables, joins, and almost any convoluted logic you may need, without worrying about if your POCO classes may or may not have the members to support it. We will see later some examples of it.

By the way, this features are the foundation for the "Dynamic Repository" pattern implementation whose explanation you can find in the article whose link is provided in the introductory one.

Some Query Methods

The 'First()', 'Last()', 'ToList()' and 'ToArray()' convenience methods are provided to, well, obtain the results you can imagine from their names. Of course you don't have to use them, and you can just enumerate this command instead.

Let me just recall, from the discussion we had about the 'Last()' method in the previous "Dynamic Records" article, that this is just a last-resort mechanism that gets and discards all possible entities until the last one is found, so you may really want to reconsider the logic you are using in your command.

The 'Skip()' and 'Take()' methods are also available, and please refer to aforementioned discussions for more details about the caveats to take into consideration when using them.

Using the Map's Cache

For performance and operational reasons each connection maintains its own cache of entities. Its deeper details are discussed in the "Internals" section below. But, among many other things, we can take advantage of it, optimizing our queries returning the entities from the cache, instead of having to hit the database.

The 'Find()' method returns immediately the first entity in the cache that matches the conditions given and, if no entity can be found in the cache then, for your convenience, it automatically goes against the database to find it. So the above query could have been written also as follows:

var reg = link.Find<Region>(x => x.ParentId == null);

Let me just point out again that this method does not create a query command you can use, but that it immediately returns a matching entity or null, either from the cache or either using the database as a last resort mechanism.

The 'cache' is quite a complex beast. Please refer to the "Internals" section below for a deeper understanding of it.

Refreshing an Entity

A common need is to 'refresh' a cached entity. This operation is performed automatically by Kerosene ORM in a number of circumstances (mostly related to the execution of 'change' operations) but, if for whatever reason, you want to be sure you are dealing with the most up-to-date contents of an entity, you can refresh it as follows:

link.Refresh(reg);

This method returns a reference to the refreshed entity that may, or may not, be the same as the original one. For any practical purposes, except some border cases, you don't have to worry about it because Kerosene ORM guarantees that the original object is also refreshed, and that both instances are kept and treated the same way from the cache perspective.

Inserting an Entity

To insert a new entity in your database it can't be easier: you just need to, well, create an instance of the entity you want to insert, and then just use the appropriate 'Insert()' method:

var reg = new Region() { ... };
link.Insert(Region).Submit();
link.SubmitChanges();

One caveat, though: remember that just creating a change command does not execute it. Indeed, Kerosene ORM is architected to implement the 'Unit Of Work' pattern so you need first to create your command, then to submit it into your link, and finally to submit all annotated changes into the database.

Using this pattern Kerosene ORM automatically opens and closes the underlying connection for you as needed and, by default, it does also create an internal transaction to execute all annotated change operations under it. Kerosene ORM does even allow you to change these defaults if you need. We will see more details later in the "Internals" section.

So you can submit into the link as many change operations as you need to get them annotated for future execution, and then executed them all at once when you are happy. If, for whatever reason, you don't want to execute the annotated operations any longer, you can discard them all by using the 'DiscardChanges()' method of your link.

...; // a change command submitted
...; // another change command submitted

link.DiscardChanges();

When this method is used, all change operations that may have been annotated into the link are discarded and disposed.

Updating an Entity

I'm pretty sure you can now imagine what follows. You change the contents of you POCO entity, and the you create and submits an 'Update()' command on it:

reg.Name = "My new name";
...; // other changes as needed

link.Update(reg).Submit();
link.SubmitChanges();

Deleting an Entity

And just to finalize this section, and risking you being bored, to delete an entity is just as easy as follows:

link.Delete(reg).Submit();
link.SubmitChanges();

Customizing a Map

While a map is not validated it can be customized and adapted for our concrete needs. This section contains the most common customizations available for 'Table' maps.

Sharing a table among many entities

As mentioned in the introduction we often face situations in which the same table in the database is shared among several POCO types in our solution. Or put this in other words: we may end up having a number of POCO classes whose contents can be found in the same table. For instance, you may have an 'Employees' table in the database, but the 'Employee' and 'Manager' classes in your solution.

Fine, no problems. Maps provide a 'Discriminator' property that can be used in these scenarios. It is basically a 'Where' condition that will be injected behind the scenes in any query we wish to execute with the map where this property is defined.

Let's suppose we are defining a map for our 'Manager' class, and that we are aware that there is a boolean column in the 'Employees' table, named 'IsManager', that we can use to filter the entities that correspond with managers. Now, even if there is not a corresponding member in our class we can customize the map we are defining as follows:

var map = new KMetaMap<Manager>(link, x => x.Employees);
map.Discriminator = { x => x.IsManager == true };

Any time a query operation is executed using this map the above logic will be injected as part of the 'WHERE' clause of the command to be executed.

Explicit Columns

As mentioned, Kerosene ORM performs a matching phase when the map is being validated, to put in correspondence columns in the database with members of the type managed by that map - and discarding those columns no corresponding members can be found.

Now, what if you want to use a concrete set of columns from your database? Fine, just specify them as the members of the 'ExplicitColumns' collection:

var map = ...;
map.ExplicitColumns.Add(x => x.Id);
map.ExplicitColumns.Add(x => x.FirstName);
map.ExplicitColumns.Add(x => x.LastName);

In this example we are telling Kerosene ORM that we are only interested in those three columns because, if the 'ExplicitColumn' collection is not empty, then its members will be the only columns taken into consideration, and the automatic discovery feature is disabled. Note that if any of the columns specified is not found in the table, then this is considered an error situation, and an exception is thrown when the map is validated.

Apart from being able to explicitly set the collection of columns we are interested in, explicit columns allow us also to specify how their contents are mapped back and forth against the type you map is managing. The explicit column's 'LoadEntityDelegate' property is invoked, if it is not null, to load into the entity the contents from that column. Correspondingly, its 'WriteRecordDelegate' is invoked, if it is not null, to write into the column the value obtained from the entity.

For instance, suppose that our 'Employee' class has a property named 'Name', that in reality is an instance of an object that has its own 'FirstName' and 'LastName' properties. We can specify our delegates in a fluent syntax way as follows:

map = ...;
map.ExplicitColumns.Add(x => x.FirstName)
   .OnLoadEntity((entity, value) => { entity.Name.FirstName = value; })
   .OnWriteRecord(entity => { return entity.Name.FirstName; });

What happens is that Kerosene ORM attaches, automatically and at run time, a package of metadata information to each entity. Among many other interesting things, this metadata package holds a 'record' instance that's used as a carry pigeon between your entities and the actual physical contents on the database's table. But the structure and contents of that record is decoupled from the structure and contents of your entity. Hence why we can have a set of columns managed by the map that may have not a direct correspondence with members of the type this map is register for.

The above 'LoadEntityDelegate' and 'WriteRecordDelegate' delegates ultimately transfer column contents back and forth that record instance. You need to be aware that this is the underlying mechanism in order to understand how Kerosene ORM Entity Maps works, and the flexibility this mechanism permits in terms of map customizations.

Forced Columns

The 'Forced Columns' feature is an advanced capability of the Kerosene ORM "Entity Maps" operational mode. It permits you to specify what columns must be taken into consideration even if there are no corresponding members in your class to hold their contents.

This feature is used to support, basically, two scenarios. The first one is when there are no corresponding members in your type that can be used to hold the primary key column, or unique valued one, that univocally identifies the entity. This is really quite an advanced and border case scenario, and we are going to postpone its discussion now.

The second one happens when we are dealing with 'navigational' or 'dependency' properties. For instance, suppose that our 'Employee' class has a 'Country' property that, well, you can imagine, holds a reference to the country instance the employee belongs to. But the table has instead a 'CountryId' foreign key column that it is not mapped in our entity because there is no correspondent member.

In this case we can specify that column as a forced one as follows:

map = ...;
map.ForcedColumns.Add(x => x.CountryId);

Forced columns provide the same delegates as the explicit columns ones. The way they will be used in this scenario is slightly different from the way they are used for explicit columns, so I will postpone the detailed discussion for the 'Eager' and 'Lazy' maps sections below.

Take note of an important difference between explicit columns and forced ones. With the former, the list of explicit columns, if it is not empty, becomes the only columns to be taken into consideration. With the later, the columns you add into the 'ForcedColumns' property are just added to the set of columns managed by the map (actually, to the set of columns to be used by the internal record), regardless of if there are corresponding members or not - but this list is not taken as the list of columns to manage.

Why have I mentioned this 'forced columns' feature here? Because it is quite common that the meanings of these two features are mixed together, and so I wanted to point out the differences out as soon as possible.

Hidden Members

It may happen that your POCO types would have a member whose name correspond to a column in the table - but that, apart from this fact, there is no logical linkage between the member and the column. Or maybe you don't want that member to be used by the map mechanism for whatever reasons. In these cases you can 'hide' that member just by doing what follows:

var map = ...;
map.HiddenMembers.Add(x => x.MyHiddenPropertyOrField);

As happens with all 'Add()' methods before, it takes a DLE (Dynamic Lambda Expression) that resolves into the name of the object you are interested at, in this case, the name of the property or field you want to hide.

Let me take here the chance to mention that the Kerosene ORM matching mechanism is quite an advanced one, so that it will try to match members in your POCO class regardless if they are fields or properties, or if they are public, protected or private ones. It can really be a lifesaver in many circumstances but, sometimes, we need to use this hidden members feature to avoid the discovery and matching mechanism for being so aggressive.

Row Versioning

Kerosene ORM provides a way by which the maps mechanism can use the row version control one your underlying database might be using, and in a very abstract and agnostic fashion. In the default case all it needs to know is the name of the column your table may be using for row control purposes:

var map = ...;
map.RowVersionColumn.SetName(x > x.TheNameOfMyRowVersionControlColumn);
  • A nice thing to mention is that this mechanism can be enabled or disabled at any time, even if the map is validated, to allow greater flexibility on how and when it is used. The map's 'RowVersionControl' property has its own 'Enabled' one that can be used at any time for this purpose.
  • The second thing to mention is that, if the name of this property is set, and if it is enabled, then row version control is enforced in any update or delete operation the map performs. If the version in the entity does not match with the version in the database an exception is thrown when these operations are executed.
  • The third thing to mention is that there must be a way to maintain the row version value associated with our entity. Typically I tend to use a private member in my POCO class for this purposes, having a name that matches the one of the column. But if this is not possible, you can always use the 'forced columns' feature for this purpose.

Finally let me address something that may be worrying you at this very moment: how these values are compared? Even if we know the concrete type used today by our database engine, this may change in the future, not to mention that each database engine can implement its own type for achieving this control.

What Kerosene ORM does is converting these values into strings for comparison purposes. 'RowVersionColumn' instances carry a default 'ValueToStringDelegate' property, with a delegate that is used to perform this conversion. Internally it uses the 'Sketch()' extension method provided by the supporting Kerosene Tools library. If you are not happy with it, you can always set this property, at any time, with your own delegate. If you use a null value then it then will revert again to the default one.

Eager and Lazy Maps

We have briefly mentioned above the concept of navigational or dependency properties. This scenario happens when our POCO class holds a reference to a parent entity, or to a list of child ones, and we want the Entity Maps mechanism to be aware of them, and to manage them appropriately.

Kerosene ORM collectively knows them as 'Extended Members', and this is the name we are going to use from now on. Note that only properties can be 'extended' ones, not fields.

Test bed scenario

Just to have some background for the following discussions let's assume that our 'Region' POCO class has a property named 'Parent' that, if not null, holds a reference to the super-region this one belongs to, that it has a 'List<Region> SubRegions' property that is a list-alike one containing the regions that depends on this one, and that, finally, it has a 'List<Country> Countries' property that contains the countries that depend directly from this region, as follows:

public class Region
{
   public string Id { get; set; }
   public string Name { get; set; }
   private object rv = null;
   
   public Region Parent { get { return _Parent; } set { _Parent = value; } }
   public List<Region> SubRegions { get { return _SubRegions; } }
   public List<Contry> Countries {
      get { return _Countries; }
      internal protected set { _Countries = value; }
   }
   
   private Region _Parent = null;
   private List<Region> _SubRegions = new List<Region>();
   private List<Country> _Countries = new List<Country>();
}

Our knowledge is that the underlying 'Regions' table has a column named 'ParentId' column whose value, if not null, is the id of the super region. We do also know that the 'Countries' table has a column named 'RegionId' whose value is the id of the region this country belongs to.

The 'Employee' and 'Region' POCO classes will have similar arrangements that we are not going to repeat here (they can be found in the examples provided in the download).

Considerations about your POCO classes

  • First thing first: you may want to remember that Kerosene ORM is a no compromises solution. As mentioned one zillion times by now, you don't have to pollute your POCO classes with any ORM-related stuff, not even with attributes, you don't have to write and maintain any external mapping or configuration files, and you don't have to make your POCO classes to derive from any ORM-specific class.
  • Second thing: think always from your business POCO classes perspective. For instance, Kerosene ORM is even able to populate your list-alike properties in the above class, if it finds them being null. But what does really have sense, from your business-logic perspective, is to populate them while constructing the entity, right?
  • Third thing: it is not longer mandatory that the classes have a parameterless constructor. If they have one, even if it is a private one, Kerosene ORM can take an optimized route to create new instances of your entities. If they have any, well, some tricks are used to instantiate empty entities when needed.

What is an 'Eager' Map

An 'Eager' map is a map that contains non-virtual extended members. This is precisely the case we have in the code above. What it is important to bear in mind is that, in this case, the contents of these members are loaded along with the contents of their hosting entity.

The main caveat lies precisely in this assertion. When using 'Eager' maps you may end up pre-loading huge object graphs, depending upon how deep and complex your extended members set-up determines. Not only it may have an impact on memory and performance (cache can grow dramatically) but also there might be noticeable delays when loading one entity... because behind the scenes you have told it to load its dependencies, and this may happen in a recurrent way with their own dependencies, and so forth.

On the flip side, once you have loaded an entity, accessing the references held in its extended members is done at 'getter' speed. Since version 6.5 Kerosene ORM implements an improved cache collector mechanism to alleviate the memory pressure - and it does a pretty good job. So, now, the above caveat only applies with tables having more than a couple of thousand records, or when the underlying communication is slow enough to impose a noticeable delay when loading the complete graph.

What is a 'Lazy' Map

A map is considered 'Lazy' when its extended members are marked as virtual. When this happens Kerosene ORM will create an internal proxy type that inherits from your original one, which carries the logic that makes possible for the extended members not to be pre-loaded along with the standard contents of your entity, but rather only when they are used. The proxy type overrides the getters and setters of your original properties in order to achieve this capability. Hence why they must be virtual.

Kerosene ORM will create and return instances of that proxy type as needed, and always as the result of a query command. But it is not mandatory that you use this proxy type explicitly. Indeed, the most common scenario is when you create an instance of your POCO class using, well, the constructor of your POCO class, and then uses it with your lazy map.

If you really want to squeeze Kerosene ORM's performance, you can create an instance of the proxy type instead:

var reg = link.NewEntity<Region>();

The type argument is your original type, not the proxy type the map may have generated. Indeed, you can use this method with any map and, if there is no proxy type available, then it just creates an instance of your original POCO class. As already mentioned, that POCO class does not even need to have a parameterless constructor for this to work.

I acknowledge that sometimes this approach is not feasible (for instance, it cannot be used with the "Dynamic Data Services" facilities - see the accompanying articles for more details). No problems: if this is the case Kerosene ORM manages your POCO class' instances in a lazy maps context as good as if they were proxied ones.

Hybrid maps

You can mix both lazy and eager maps as you wish, and even you may have both virtual and non-virtual extended members in the same class for any reasons. When these situations happen then you are said to be in a hybrid maps scenario, which is completely supported by Kerosene ORM without any caveats.

Creating a custom map

The recommended approach to specify extended members is by using a custom map for the type you are interested in. The class of this map will inherit from the KMetaMap<T> one, and its constructor is the right place where to use the methods and properties we are going to see below (as well as the ones we have discussed already).

For instance, a custom map for our Region POCO class can be defined as follows:

public class RegionMap : KMetaMap<Region>
{
   public RegionMap : base(link, x => x.Regions)
   {
      ...
   }
}

We need to bear in mind that the base KMetaMap<T> class has a constructor that takes two parameters: the first one being the link instance where this map is going to be registered into, and the second one being a DLE that resolves into the name of the primary table where to find the entities managed by this map.

Later on, your can register a new instance of our customized map into a given link as follows:

if(link.Map<Region>() == null) new RegionMap(link);

You may want to remember from the previous articles that validation takes place automatically by default, so once you have registered the map you can consider it available, without any further actions from your side, for any practical purposes.

Specifying a Parent dependency

Let's now see how can we specify a parent extended member inside that constructor. In this case, remember that what we want to do is to tell Kerosene ORM that our POCO class has a property named 'Parent', that holds a reference to, well, the region this one depends from, and that this one is maintained in the database using the 'ParentId' foreign key column:

   ...
   
   ForcedColumns
      .Add(x => x.ParentId)
      .OnWriteRecord(obj => {
         return obj.Parent == null ? null : obj.Parent.Id;
      });
      
   ExtendedMembers
      .Add(x => x.Parent)
      .SetMode(KMapExtendedMemberMode.Parent)
      .OnComplete((obj, rec) => {
         return Link.Find<Region>(x => x.Id == rec[y => y.ParentId]);
      });
   
   ...

Because our 'Region' class has no member available to store the value of that foreign key column, we have firstly added a forced column to the map in order to have a place where to store its value - and, at the end of the day, remember it will be a column in the 'record' that will be associated at run time with each managed entity.

We have also set the 'WriteRecordDelegate' property of that forced column, so that we can feed the record from the entity when needed. I'm sure you have noticed that we have not set the 'LoadEntityDelegate' property - and the reason is because, when we are dealing with extended members, how to obtain the entities they refer to is the job of the extended members themselves.

So, secondly, we have added the extended member itself. The 'Add()' method takes a DLE that resolves into the name of the receiving member in the type. Remember that we can only use properties, not fields, but they can be public, protected, or internal protected ones. Private members are not supported for lazy maps due to some limitations on C# itself. If the member is marked as virtual then we will have a 'Lazy' map. If not, an 'Eager' one.

The 'SetMode()' method permits us to specify how the extended member behaves when it is involved in a change operation of its hosting entity. The default value is 'None', which means that the extended member is not involved in any change operation of its hosting entity. In our case we have specified a value of 'Parent', which means that the extended member is, well, to be considered as a parent of its hosting entity.

Finally we have specified what happens when the hosting entity is completed, setting its 'CompleteDelegate' property that, conceptually, replaces the 'LoadEntityDelegate' one of the forced columns. This 'CompleteDelegate' is invoked when the hosting entity has been loaded and there is the need to load its extended properties. If they are eager ones, then this delegate is invoked as soon as possible. If they are lazy ones, this delegate is only invoked when their getters are used. In this later case Kerosene ORM manages 'dirty members' such a way that the delegate is only invoked when the member can be considered 'dirty' from the maps mechanism perspective.

The signature of this delegate is 'Func<T, IKRecord, object>', taking as its arguments the entity being completed and the record where to find the contents obtained from the database. You can then perform any logic you wish, and to execute any database operations you need, in order the delegate to return a reference that will be loaded into the extended member. This is why, in the above example, we have used:

   ...
      .OnComplete((obj, rec) => {
         return Link.Find<Region>(x => x.Id == rec[y => y.ParentId]);
      })  
   ...

In this example we are using the value of the 'ParentId' column that is stored in the record to perform a query, using the 'Find()' method that returns either the reference of the super-region or null if such cannot be found in the cache or in the database. Remember that maps have a 'Link' property, so we can use it to perform the find operation. If you need to use a different data context, or to use any other logic to generate and return the parent entity, please feel free to do so.

As a side note we had to use a different dynamic argument in the lambda expression used on the record to specify the name of the column. This is needed due to the way lambda expressions work in C# - in our case we are basically using a nested lambda expression inside an external one, and the compiler will complain if we would have used the same name for their respective dynamic arguments.

Specifying a Child dependency

Let's now move on and see how to specify child extended members:

   ...
      
   ExtendedMembers
      .Add(x => x.Countries)
      .SetMode(KMapExtendedMemberMode.Child)
      .OnComplete((obj, rec) => {
         return Link.Where<Country>(x => x.RegionId == obj.Id).ToList();
      });
   
   ...

So in this case what we are trying to do is to load the 'Countries' member with the list of countries that depend directly from this region. We don't need to use, in this case, any forced column, as all values we will need are already stored in the entity instance itself.

The 'Add()' and 'SetMode()' methods are the same as in the previous case and I won't repeat their discussion here again. The only thing to mention is that the 'mode' of the extended member is set to 'Child'.

The 'OnComplete()' method has also the same syntax as in the previous case but now it is returning a list or references instead of a single reference. We have not used in this case the 'IKRecord' argument because, well, we don't need it here.

As a final consideration, if the receiving member is a list-alike one, then Kerosene ORM treats it slightly different as if it were a plain reference one. In the later case, remember that whatever instance is returned from the delegate is fed into the receiving member. But when it is a list-alike one, then the original list is cleared, and then the references contained in the list returned by the delegate are added into it. In no circumstances the list returned is used to set the value of the receiving member.

There is one consideration regarding this rule, though. When the receiving member is null then Kerosene ORM will firstly creates a list for that member (if the list returned is not null), and then the list of references obtained is added to that list as described above. This is really a border case, but it worth to be mentioned.

How the Extended Properties are handled

Ok, we have specified Parent and Child extended members, but we would like to have some more information about how they are handled by Kerosene ORM. No problems, this section provides precisely this information.

Probably the most important concept to talk about is how these 'parent' and 'child' constraints are honored in different scenarios.

For starters, let's suppose we are asked to delete the top-most region. Remember that in our scenario we can do it just as follows:

var reg = link.Find<Region>(x => x.ParentId == null);
if(reg != null) link.Delete(reg).Submit();
link.SubmitChanges();

When the delete command is submitted, it takes into consideration the dependencies defined in the extended members of the map. In our case we have three ones to consider:

  • The parent region: as it is null, because it is the top-most region, it will be just ignored. Indeed, in delete operations, parent constraints are taken just as an indication to refresh the parent entity after all operations have been executed.
  • The child regions and countries: in this case these lists are defined as child dependencies. Because we are in a delete context Kerosene ORM will obtain their contents and submit an automatic delete operation for each reference contained in those lists, as expected. Remember that these references hold instances of the child regions and countries, and that this later ones hold a list of references to the employees that belong to that country. So the net effect is that, in this example, when 'SubmitChanges()' is invoked, it will find a list of pending (submitted) delete operations that will delete all contents from the database.

Let's now analize the opposite operation: insertions. Suppose that, starting from this empty database, our application creates some instances with dependencies among them:

Region root = new Region() { Id = "000", Name = "Worl Wide" };
Region emea = new Region() { Id = "100", Name = "EMEA", Parent = root }; root.SubRegions.Add(emea);
Region amer = new Region() { Id = "200", Name = "AMER", Parent = root }; root.SubRegions.Add(amer);
Region apac = new Region() { Id = "300", Name = "APAC", Parent = root }; root.SubRegions.Add(apac);

We are still at application-level logic (the right place to be, by the way), so when we have created a child instance we have set the property that 'points to' its parent, and in that such parent we have included our new reference into its corresponding list - nothing new, this is probably the way we have used more often in almost all applications we have encountered fo far.

Now, we want to persist this structure into the database. Sure, we can submit an insert operation per each new instance, but you lazy guy are fortunate because Kerosene ORM can handle those dependencies for you automatically. So you just need to submit an insert operation on the top-most instance you have in your application-level logic and you are done:

link.Insert(root).Submit();
link.SubmitChanges();

The first line, remember, creates an insert operation (on the root instance in this example) and then submits it into the link for further execution. When this submission happens Kerosene ORM analyzes the dependencies of the entity affected by the operation. In our case, it finds it has a list of child regions, and because the parent-child relationship in place it creates and submit an internal insert operation per each child instance that has not been inserted yet.

The second line just materializes all the changes into the database, inserting all the entities we have created in the above example. What we have achieved in this example is known as 'cascading down' because we have started from a parent entity and have left Kerosene ORM to cascade down through the dependencies.

Let's now see an example of the opposite 'cascade up' modality. Let's assume we are starting from the same clean database, and that in this example our application creates some entities as follows:

Region root = new Region() { Id = "000", Name = "Worl Wide" };
Region emea = new Region() { Id = "100", Name = "EMEA", Parent = root }; root.SubRegions.Add(emea);
Region amer = new Region() { Id = "200", Name = "AMER", Parent = root }; root.SubRegions.Add(amer);
Region apac = new Region() { Id = "300", Name = "APAC", Parent = root }; root.SubRegions.Add(apac);
Region enorth = new Region() { Id = "110", Name = "ENORTH", Parent = emea }; emea.SubRegions.Add(enorth);
Country uk = new Country() { Id = "uk", Name = "UK", Region = enorth }; enorth.Countries.Add(uk);

Note that we are using a more interesting scenario, involving entities of different POCO classes. Now, instead of submitting an insert operation on the top-most instance we can just do the opposite: submit the insert operation on the child-most one:

link.Insert(uk).Submit();
link.SubmitChanges();

As expected Kerosene ORM realizes that our 'uk' entity depends from the 'enorth' one and, because it is not persisted yet, submits an automatic insert operation for it. And the ball keeps rolling so now the library treats the 'emea' entity, and in turn then the 'root' one.

You get the idea, right? At each stage Kerosene ORM analyzes the dependencies of the entity at hand and processes them appropriately - so as far as there are clear dependencies defined at you application-level related logic, it really doesn't matter if you submit a change operation on the top-most instance, on the child-most one, or on any instance in between. For instance, in the above example, when reaching the 'root' instance the library realizes not only that it has to be inserted, but also that it has its own child entities that may need to be processed as well.

The process we have described work the same way for Delete, Insert, and Update operations. The difference lies on how parent and child constraints are handled, because deleting a parent implies that it needs to delete its child dependencies, whereas inserting a child needs to make sure its parent entity, if any, has been persisted into the database before proceeding.

There is one caveat to take into consideration. Let me draw a picture to better explain it:

Cascading Example

Suppose we have submitted a change operation on the left entity. As mentioned, Kerosene ORM analyzes its dependencies and have found that it depends on a given parent. So, in turn, the library will analyze its own dependencies. When doing so it reaches a parent's child that has been already persisted and that has no updates pending in its contents. In this situation, Kerosene ORM will stop the loop here... without realizing that this child has, in turn, a child that has not been persisted yet.

This is done by design, to prevent loading the complete database in memory. But the side effect is that the child-child entity at the right, that may or may not need a change operation, is not touched. This is really a border case because I'm pretty sure that, if it would be needed, you would have already submitted the appropriate change operation on the child-child entity as it was not directly related to the original one. The morale is that you can rely on the default mechanism as far as there is a direct parent-child relationship, but not when there are only sibling ones. Since you may encounter this situation it worth to be mentioned here.

Two more things about these topics:

  • The first one is that when a change operation is annotated (submitted) on an entity, then trying to annotate another change operation is considered, in the general case, an error, and an exception will be thrown. You will see below how to find if a given entity has or not a pending operation attached to it. Also, Kerosene ORM implements some safety mechanisms by which if the type of the new change operation is the same as the existing one, the new one is just discarded without complains.
  • Also, when processing the dependencies and creating and submitting the internal change operations mentioned above, the library checks whether there is, or not, one already annotated into the entity, and prevent submitting any again. It may also happen that Kerosene ORM discards your operation if it not fits into the logic being processed. For instance, suppose you have submitted an insert operation on a child operation, and then its parent is deleted. In this case that insert operation is just disposed because it has no sense. Other similar cases are treated accordingly.

Let me finalize this section by describing how Kerosene ORM can identify if there are any changes in the contents of an entity, and so an update command is required. You may want to remember that each entity has a package of metadata information associated to it, and among other things it maintains an 'record' instance that contains the last contents persisted to or retrieved from the database. What Kerosene ORM does it to compare these values with the current ones in the entity, and if there is any mismatch then an update operation will be created and submitted.

Entity Maps Internals

Man (or madam)! What a long article! You deserve all my credits for being able to stay with me for so long. Fortunately we have just one more section ahead, and it is a fun one: let's dive into the internals of the Kerosene ORM's Entity Maps mechanism.

Meta Entities

As was briefly mentioned above Kerosene ORM attaches, automatically and at run time, a package of metadata information to each entity it has to manage. This package is known as a 'meta entity', and object that implements the 'IKMetaEntity' interface, and it has a number of interesting properties:

  • The 'Entity' one, that holds a weak reference to the entity this package is attached to. Being 'weak' it means that, if the source entity is no longer in use, or it may have been collected by the GC, then its value will be null. Actually, Kerosene ORM interprets this fact as a signal to discard this package from its internal structures.
  • The 'MetaMap' one that holds a reference to the map that has been involved in the latest database operation that affected this entity, it being a query or a change one. This property can be null when, for instance, you have created an instance of your POCO class and not have yet executed any database related operation.
  • The 'State' property that returns a value that let us know the current state of our entity from the Entity Maps mechanism perspective. 'Collected' means that the entity is no any longer in use, or that the GC has collected it: in this cases the above 'Entity' property also returns null. 'Detached' means that our entity is not associated with any map, which may happen when it is a brand new entity not yet inserted, when it has been deleted, or when it has been explicitly detached from the cache. The 'ToInsert', 'ToDelete' and 'ToUpdate' values reflect that there is a submitted change operation associated with our entity. Finally, the 'Ready' value means that the entity has been successfully persisted to, or retrieved from, the database.
  • The 'MetaOperation' property, if it is not null, returns a reference to an object that describes the pending change operation that has been submitted (annotated) on this entity.

Fine, but how can we get access to the metadata package associated with a given entity? The 'KMap' factory static class provides you with the 'MetaEntity()' static method that can be used for this purpose:

var obj = ...; // obtain your business-level entity, an instance of your POCO class
var meta = KMap.MetaEntity(obj);

That's it. If your entity does not yet carry the package of metadata information then it is created on the fly and returned.

Finally, there is yet another interesting property: the 'Record' one. Its job is to maintain the latest records obtained from the database or persisted into it. This property is provided for your convenience, in case you want to take a look at its latest contents, or at the schema that describes the structure of columns the map has taken into consideration (by using its 'Schema' property).

Kerosene ORM relies heavily on this object. For instance, when defining forced columns the library will make sure that these columns appear in this record even if there were no corresponding members in the type being mapped. This would be also the case when using dependency properties with no members in the type to hold the appropriate columns in the table, or when your identity columns have no corresponding members in your POCO class. And, of course, its contents are the ones the library considers that reflect the most up-to-date information regarding the real ones in the database. For all those reasons you are strongly advised not to mess up with this record, not to modify it in any way, not to dispose it, etc.

The way Kerosene ORM attaches, at run-time, this package of metadata information is basically by treating it as an attribute, creating and adding it into the list of attributes carried in the object's run-time metadata when needed. There are some safety nets also implemented to guarantee that only one package of metadata exist for any given object.

Meta Links

The core 'IKLink' objects, that remember they represent an agnostic connection, or data context, against an underlying database, are not prepared per-se to deal with maps. But do remember as well that they provide a way to carry any arbitrary information that might be needed using its 'ExtendedInfo' extensibility mechanism.

What the Entity Maps mechanism does is to create a meta-link, prepared to deal with maps, and then add this instance to the extended information repository of the original link. At the end of the day you don't have really to be very picky with this because the library provides a complete set of extension methods on your original link instance that give visibility to the functionality provided by the meta-link.

Anyhow, if for whatever reason you want to have direct access to it, the 'KMap' static factory class allows you to do so by using an extension method of your original link instance, as follows:

var link = ...; // My original link instance
var metaLink = link.MetaLink();

The Meta Cache

Each data context, or link (well, actually, the meta-link associated with it), maintains a cache of managed entities. This cache is populated when, for instance, performing a query so that these entities are ready to be used by your application without having to go again to the database to fetch them. It is also populated or purged when performing the appropriate change operations on your entities.

Once an entity is stored in the cache it can be also located by the 'Find()' and 'Refresh()' operations as needed. Also, when executing a query, Kerosene ORM will try to locate in the cache any entities whose identities correspond to the records obtained from the database, and in this case, the library updates their contents instead of creating new entities. This way, any references held by your application are automatically updated with the most fresh contents obtained.

When a delete operation is executed the corresponding entity is removed from the cache, and its package of metadata information is cleared. If for whatever reason you want to remove one of your entities from the cache, without executing a delete operation, it can be achieved by using:

var link = ...; // my data context
var obj = ...; // my entity obtained from any source
link.Detach(obj);

Note that this will render the entity as if it were just created by your application, losing whatever state information or record it might be carrying.

The internal management of the cache is very complex. For instance, we need to identify what entities have been collected by the GC, so removing them from the cache. We also want to balance the size of the cache against having a complete and in-memory updated representation of the contents of our database, because as it grows the impact on memory and performance becomes noticeable.

Kerosene ORM implements an internal cache collector that does a pretty good job balancing those needs, and it is enabled by default. You can disable it if you wish by using the 'link.DisableCollector()' method, but I encourage you to think twice about why do you want to do so. I give it some use in debug scenarios, to test memory pressure and impacts on performance, but I have never used it in production ones.

If it is disabled and you want to enable it again you can use the 'link.EnableCollector()' method. You can also use an override of this method to change the operational configuration of the collector while it is running: 'link.EnableCollector(int ms, bool gc)'. The first argument is the number of milliseconds to wait till the cache collector is fired for this link instance. The second one tells the library whether to force a GC collection before firing the cache collector. You can also get back information on whether the cache collector is enabled for a given link or not by using the 'link.CollectorEnabled()' method.

Other Advanced Considerations

Customizing Change Transactions

We already know that Kerosene ORM will create or use an internal nestable transaction to protect all the change operations executed in a shot. This is really the recommended way of executing changes against the database. But you can customize and adapt this mechanism for your own needs:

var link = ...; // my link
bool useTx = link.UseTransactionsWhenSubmitChanges();
link.UseTransactionsWhenSubmitChanges(false);

The first method retrieves whether automatic nestable transactions are enabled on the link or not (which are by default). The second method permits us to set the new value.

Even if we have modified the above setting we can, for a particular scenario, execute all pending changes with or without transactions explicitly, by using, for instance:

link.SubmitChanges(withTransaction: false);

Note that if we have used transactions, and any error has happened, Kerosene ORM can make a number of educated guesses about the state of the database, and so it just to need to refresh the entities in its cache. When we have opted for not using transactions, then the situation is not as easy, as some operations may have been persisted, whereas others not. To recover from such scenario, Kerosene ORM tries to un-play the change operations that may have been actually executed, which is not always possible to its complete extension.

Anyhow, in both cases, if a database error has been detected while executing the transactions, apart from trying to recover from that condition, Kerosene ORM will finally throw an exception reflecting this situation. Your application level code can ignore this exception or use it the way it better fits into its needs.

Entities with no parameterless constructor

If your entities have not a parameterless constructor then, when the need comes to create new instances, Kerosene ORM will use a hidden gem provided by the .NET serialization services, which is the 'FormatterServices.GetUninitializedObject(type)' one. As the documentation says this method will create a "zeroed object of the specified type", which is precisely what we wanted.

Complex Map Queries

On top of the standard methods the 'Query' command supports, Kerosene ORM adds support for some additional ones that open the door to complex query scenarios.

Let's suppose that our query logic involves other tables. Chances are high that a column in our primary table ('Id', etc.) has the same name as the column in any of those other tables: we need to use aliases. But how? Easy, use the 'TableAlias(alias)' method whose DLE (dynamic lambda expression) argument resolves into the name of the alias to use, just for this query command, with the primary table:

var link = ...M
var cmd = link.Query<Region>()
   .TableAlias(x => x.Reg)
   .From(x => x.Countries.As(x.Ctry))
   ...;

This way we can involve in our query logic other tables, using the 'From()' method, each having assigned its own alias assigned. We can also use this primary table alias assignation feature if, for whatever reason, we want to use joins in our query:

var link = ...M
var cmd = link.Query<Region>()
   .TableAlias(x => x.Reg)
   .Join(x => ...)
   ...;

Usage of joins in the context of a map operation is extremely delicate. It was introduced to support a very specific need. But even if it is now supported is one of those border cases I am sure I am going to find just once again, if any... and in a far future I guess.

What else?

Now that you have complete information about this "Entity Maps" operational mode you may want to understand how Kerosene ORM permits you to implement an unique Dynamic Repository and Unit Of Work patterns. To do so please refer to the link that is given in the introductory article mentioned above.

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

 
QuestionPut your code in Github Pinmemberdelimavi3-Sep-12 0:37 
Nice job, good thought. It will be better i think if you put codes in github for contribution and change tracking.
AnswerRe: Put your code in Github PinmemberMoises Barba3-Sep-12 4:45 
GeneralRe: Put your code in Github Pinmemberdelimavi3-Sep-12 5:09 
GeneralRe: Put your code in Github PinmemberMoises Barba6-Sep-12 9:35 
GeneralRe: Put your code in Github Pinmemberdelimavi6-Sep-12 22:01 
AnswerRe: Put your code in Github PinpremiumMoises Barba11-Mar-14 23:10 
GeneralMy vote of 5 Pinmembervisalia2-Sep-12 8:35 
GeneralRe: My vote of 5 PinmemberMoises Barba3-Sep-12 4:45 
GeneralRe: My vote of 5 PinpremiumMoises Barba11-Mar-14 23:10 
GeneralMy vote of 4 PinmemberChristian Amado2-Sep-12 5:12 
GeneralRe: My vote of 4 PinmemberMoises Barba3-Sep-12 4:46 
GeneralNew question for you :) PinmemberSimon_Whitehead16-May-11 19:03 
GeneralRe: New question for you :) Pinmembermbarbac17-May-11 4:59 
GeneralRe: New question for you :) PinmemberSimon_Whitehead17-May-11 12:21 
GeneralRe: New question for you :) PinpremiumMoises Barba11-Mar-14 23:11 
GeneralMy vote of 5 Pinmembervbfengshui2-May-11 7:45 
GeneralRe: My vote of 5 Pinmembermbarbac2-May-11 13:04 
QuestionIssue with DateTime? PinmemberSimon_Whitehead16-Feb-11 12:31 
AnswerRe: Issue with DateTime? PinmemberSimon_Whitehead16-Feb-11 13:09 
GeneralRe: Issue with DateTime? Pinmembermbarbac17-Feb-11 21:04 
GeneralRe: Issue with DateTime? PinmemberSimon_Whitehead17-Feb-11 21:17 
GeneralRe: Issue with DateTime? Pinmembermbarbac17-Feb-11 23:29 
GeneralRe: Issue with DateTime? PinmemberSimon_Whitehead18-Feb-11 0:25 
GeneralRe: Issue with DateTime? Pinmembermbarbac26-Mar-11 8:48 
GeneralIssue with IsUnique/IsKey? [modified] PinmemberchemicalNova14-Feb-11 15:06 

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 | Mobile
Web04 | 2.8.140814.1 | Last Updated 11 Mar 2014
Article Copyright 2010 by Moises Barba
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid