An ORM
aims to make it easier to focus on the logic around entity objects, while saving things to a database is pretty much done for you.
This article covers specific anomalies you may encounter while using an ORM
. It also offers suggestions, how to deal with it.
This article won’t go into the basics of using ORM
. There’s other resources for that.
This information was gathered from experience, built up with NHibernate
. It might be possible that other ORM's
have similar issues, due to how ORM's
work internally.
You might not want to map binary and other serialized data fields using ORM
, because it can harm performance quite a bit.
Retrieving some loose fields of an entity, would also retrieve a blob in that case. As well as saving a whole blob, when changing just a few fields. That data transmission can be quite a bottle-neck sometimes, especially in a multi-user environment.
Using separate SQL
statements for retrieving blobs might be a better alternative.
It seems ORM's
like it when you first read the data out, and then start writing to it. Not read, write some, read a little more, write some more. It may have to do with its way of querying the database, caching things and how it handles committed and uncommitted objects.
An bridge entity applies to n => n
relationships and may require an additional table to make the link between the entities:
Using an ORM
, the bridge entity might not be visible in the code, but can be managed as two collections inside the two main entities:
class Question
{
IList<Category> Categories { get; set; }
}
class Category
{
IList<Question> Questions { get; set; }
}
The ORM
can do quite a bit of magic under the hood, to keep these collections in sync. Perhaps a little too much for its own good. You might expect quite a few Exceptions
to go off, while ORM
tries to guard the integrity of the relationship.
These problems almost all go away, if you map a bridge entity instead. This turns the n => n
relationship into two 1 => n
relationships which ORM
can manage with less hardship. You can let both entities hold a list of bridge entities instead. In turn, the bridge entity would link back to the two main entities:
class QuestionCategory
{
Question Question { get; set;}
Category Category { get; set;}
}
class Question
{
IList<QuestionCategory> QuestionCategories { get; set; }
}
class Category
{
IList<QuestionCategory> CategoryQuestions { get; set; }
}
This also has the advantage, that the entity model would not need to be refactored, if you’d want to add properties to a combination of things.
It might be advised, that the bridge table not rely on a composite key of the two ID's
. A single surrogate ID
might do better:
This is because it gives 1 handle to the combination of 2 thing. This gives ORM
less difficulty managing things under the hood, prevents passing around composite keys, lower quality hash codes, URLs that don’t look pretty, etc.
Particular surprises might emerge when using inheritance in your entity model at least while working with NHibernate
. The main advice is to avoid inheritance at all in the entity models if you can.
When retrieving an entity through ORM
, it will likely not return an instance of your entity type, but an instance of a type derived from your entity, a so called Proxy
. This Proxy
adds to your entity a sort of connectedness to the database.
When you retrieved an entity from NHibernate
that has inheritance, using the base type it returns a Proxy
of the base type instead of a Proxy
of the derived type, which makes reference comparisons between base Proxies
and derived class Proxies
fail.
But you can also get failing reference comparisons another way. If you Unproxied
a derived type, and retrieve another Proxy
of the derived type, reference comparison might also fail.
It can also harm performance of queries, getting a lot of left joins
: one for each derived class’ table.
You can then Unproxy
both and it will return the underlying object, which is indeed of the derived class, upon which reference comparison succeeds.
To evaluate the type, you are better of Unproxying
as well. Otherwise it will compare Proxy
types instead of your entity type. This can be confusing.
ID comparison could avoid this problem that surrounds entity equality checks.
An alternative for inheritance might be, to use a 1-to-1
related object to represent the base of the entity. Although, NHibernate
and other ORM's
are not a fan of 1 => 1
relationships either. What may save the day, is to map the relationship one-way only and not bidirectionally, so the ORM
gets less confused.
Letting two entity types use a mutual interface
might be an alternative too.
By now maybe it may be clear, that the main advice is not to use inheritance in the first place in your entity models, if at all possible.
Data access in this architecture is favored behind generic interfaces from JJ.Framework.Data
.
Here is something that happens in ORM
sometimes:
Some methods of data retrieval work with uncommitted / non-flushed entities: so things that are newly created, and not yet committed to the data store. Other methods of data retrieval do the opposite: only returning committed / flushed entities. This asymmetry might be common in ORM's
, since doing it another way might harm performance considerably:
Method | Data Read |
---|---|
IContext.Query |
committed |
IContext.Get |
1st committed, then uncommitted |
IContext.TryGet |
1st committed, then uncommitted |
Navigation properties / following the object graph |
1st committed, then uncommitted |
It appears to have to do with, when the ORM
goes to the database to query for objects.
Flushing
in NHibernate
would mean that all the pending SQL
statements are executed onto the database, without committing the transaction yet.
A Flush
can help get an auto-generated ID
from the database. Also, sometimes when NHibernate
is confused about the order in which to execute things, a Flush
may help it execute things in the right order.
The trouble with Flush
is, that it might be executed when things are not done yet, and incomplete data might go to the database, upon which database may give an error. So it is a thing to use sparsely only with a good reason, because you can expect some side-effects.
Flushes
might also go off automatically. Sometimes NHibernate
wants to get a data-store generated ID. This can happen calling Save
on an entity. Unlike the documentation suggests, FlushMode.Never
or FlushMode.Commit
may not prevent these intermediate flushes.
Upon saving a parent object, child objects might be flushed too. Internally then NHibernate
asked itself the question if the child object was Transient
and while doing so, it apparently wanted to get its identity, by executing an insert
statement onto the data store. This once caused a null
Exception
on the child object’s ParentID
column.
It may also help to create entities in a specific order (e.g. parent object first, child objects second) or choose an identity generation scheme, that does not require flushing an entity pre-maturely (like a Database Sequence
or Guids
).
Entity Framework
is a framework for data access, a so called ORM
(Object Relational Mapper). Entity Framework
might be hidden behind abstractions using JJ.Framework.Data.EntityFramework
and repository interfaces.
At one point we noticed a slow down in JJ.Framework.Data.EntityFramework
. But it hadn’t even been modified. Probably caused by an upgrade to a newer version of Entity Framework
. Unfortunately JJ.Framework.Data.EntityFramework
was not upgraded since then. The reason was most apps used NHibernate
instead.
When using Entity Framework
, transactions might not work unless you enable MSDTC
(Microsoft Distributed Transaction Coordinator). That is a Windows
service belonging to the SQL Server
installation.
NHibernate
is a technology used for data access. A so called ORM
(Object Relational Mapper). It is comparable to Entity Framework
.
NHibernate
is used in some projects, because an employer favored it, and other projects joined the club.
NHibernate
might be hidden behind abstractions using JJ.Framework.Data.NHibernate
and repository interfaces.
If all this makes you lose grip on reality and wonder whether ORM's
are really worth it? Well, they can be. They allow you to program focusing on the meaning of things, rather than how to store it. Even though that is ambiguous because the story above suggests you’d still be better off knowing what it does and how it does it. You just don’t need to do it yourself anymore.