I am using NH 2.1.2.4.
I have the query cache setup with Fluent NHibernate like this:
MsSqlConfiguration.MsSql2005
.Cache(c => c.UseQueryCache()
.ProviderClass(typeof(NHibernate.Caches.SysCache2.SysCacheProvider)))
My criteria looks like this:
Session.CreateCriteria<Employee>()
.Add(Restrictions.Eq("Username", username))
.SetMaxResults(1)
.SetCacheable(true)
.UniqueResult<Employee>();
However, when I use SQL Profiler I can see the query is still always being executed against the database. No inserts or updates to the Employee table are being made.
Why is it not working?
My Employee mapping class specifies Employee as cachable like this:
public sealed class EmployeeDbMap : ClassMap<Employee>
{
public EmployeeDbMap()
{
....
Cache.ReadWrite();
}
}
BTW I see there are a number of related questions on Stackoverflow, but none seem to have good answers: here and here.
If you are not caching the Employee entity too, a query will be needed to load it, as the query cache stores the results as ids.
Also, NHibernate cache only works if you do all your work (including queries) inside transactions.
Related
I have a following (simplified) hierarchy:
class Account
{
AccountType Type;
long Id;
string Name;
}
public enum AccountType
{
UsualAccount = 0,
SpecialAccount = 1
}
class SpecialAccount : Account
{
List<SpecialItem> SpecialItems;
}
class SpecialItem
{
long Id;
string Name;
}
To help NHibernate to detect subclass SpecialItem, I use the following code in the Account mapping:
<discriminator column="Type" formula="Type"/>
and SpecialItems have lazy="false" set in the mapping, but as far as I know, it is ignored during LINQ queries.
Now when I use LINQ and call
Session.Query<Account>().ToList();
I see that SpecialItems are fetched in a separate query. I would like to load them eagerly.
I could do that using
Session.Query<Account>().FetchMany(a => a.SpecialItems).ToList();
but there is a problem - SpecialItems is the property of the SpecialAccount. So I somehow need FetchMany to work only if a is a SpecialAccount class.
I tried even something as ugly as:
Session.Query<Account>().
FetchMany(a => (a is SpecialAccount ? (a as SpecialAccount ).SpecialItems : null));
but NHibernate still selected SpecialItems in a separate query.
How can I make NHibernate select SpecialItems for SpecialAccount in a single query?
Additional information
I am using MS SQL Express 2012 and NHibernate configuration has the following lines:
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="adonet.batch_size">50</property>
<property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
NHibernate.dll version 3.2.0.4000
Here are the queries generated by NHibernate for one existing SpecialAccount with Id=2, as they appear in MS SQL Profiler (with selected events RPCCompleted, Exec Prepared SQL, SQL:StmtCompleted):
select account0_.Id as Id5_, account0_.Type as Type5_, account0_.Name as Name5_, account0_.Type as clazz_ from tAccount account0_
exec sp_executesql N'SELECT specialite0_.AccountId as Id1_, specialite0_.Id as specialite1_1_, specialite0_.Id as specialite1_13_0_, specialite0_.Name as Name13_0_
FROM tSpecialItem specialite0_ WHERE specialite0_.Id=#p0',N'#p0 bigint',#p0=2
You can't to a "conditional fetch". You need at least two queries.
You can, however, group them using a Future LINQ query, so it would be effectively one roundtrip:
session.Query<SpecialAccount>().FetchMany(x => x.SpecialItems).ToFuture();
var accounts = session.Query<Account>().ToFuture();
Both queries run when you enumerate accounts. We don't store the results of the first query explicitly, but the SpecialAccounts are loaded in memory with their corresponding SpecialItems collections, so there are no extra DB calls.
With my only ORM knowledge being L2S/EF, I was surprised when the following code inserted a row into the database before I called repo.Save:
var repo = new UserRepository();
var user = new User { Name = "test" }
repo.Add(user);
//repo.Save();
Repo looks like this:
public void Add(T entity)
{
session.Save(entity);
}
public void Save()
{
session.Flush();
}
After some digging, it seems NHibernate needs to make the insert happen right away in order to get the ID of the new entity (since it's using an auto increment ID). But L2S/EF doesn't work like this; I can add many entities and save them all at the end.
Question is: is there a way to achieve the same thing with NHibernate, while still using auto increment IDs, and out of interest does anyone know why it works like this?
Fabio Maulo already blogged about the usage of identity generator a few times. The answer is: use hilo, guid.comb or something like this.
NHibernate needs the identity because every entity in the session (they are called "persistent entities") needs to be identified. The identity is also normally used to determine if the record already exists in the database (unsaved value).
session.Save actually only makes a transient entity persistent. When the database is generating the id, it needs to be stored to get the id. If NH can create the id itself (eg using hilo), it could be stored next time when the session gets flushed.
I have a product table that has a many-to-many relation to itself (using a two-column many-to-many table) and I have set it up in Fluent NHibernate with the following code:
public class ProductConfiguration : ClassMap<Product>
{
public ProductConfiguration()
{
Table("Product");
Id(p => p.Id).GeneratedBy.Guid();
Map(p => p.Name).Not.Nullable().Length(254);
Map(p => p.Description).Not.Nullable().Length(1000);
Map(p => p.CreatedAt).Not.Nullable();
HasManyToMany(p => p.CrossSell)
.Table("ProductCrossSell")
.ParentKeyColumn("Id")
.ChildKeyColumn("ProductId");
}
}
My MVC application has two pages that uses this setup:
Index - Uses a generic repository GetAll method to display all products.
Detail - Uses a generic repository GetById method to display one product and any related cross sell products setup in the many-to-many realation.
It looks like NHibernate is set to LazyLoad the many-to-many by default so when I fire up the application and watch it in profiler I can see that it does LazyLoad the many-to-many with the following alert "Use of implicit transactions is discouraged".
How do I get rid of this alert? I couldn't find any information on how to wrap a LazyLoad inside a transaction to get rid the alert. Is it even possible?
Is there a way to not lazyload this by telling NHibernate that whenever I ask for GetById make sure to join the tables a get everything in one query? I tried using .Fetch.Join() in the many-to-many mapping but that also affected my GetAll query which now displays a joined result set as well which is incorrect.
What is the best apprach for this kind of simple scenario?
Thanks
The way to get rid of the warning is to access the object graph and fully populate the UI elements inside a single transaction.
Not by configuration. You can create an HQL query that eager fetches the association and use that query for a specific view. I would stick with lazy loading and not make that optimization unless needed. The HQL would be:
return session.CreateQuery("from ProductionConfiguration pc join fetch pc.CrossSell where pc.Id = ?")
.SetGuid(0, id)
.List<ProductConfiguration>();
All collections are lazily loaded in NHibernate by default.
You must be triggering loading with a call of some kind (maybe even with the debugger watches)
I have a simple model class (Part), which pulls from it's information from a single table (t_Part).
I would like a subclass of this model called (ProducedPart), that would still utilize NHibernate's caching mechanisms, but would only be instances of (Part) that have a foreign key relationship in a table called "t_PartProduction". I do not need to have a model for this second table.
I only need a read-only version of ProducedPart
I could always implement a Facade/Repository over this, but I was hoping to setup a mapping that would pull "t_Part" joined with "PartProduction" when I asked for "ProducedPart" in NH.
Is this the wrong way to use NH?
Edit
So, the SQL would look something like
SELECT p.*
FROM t_Part p
INNER JOIN t_PartProduction pp ON pp.PartID = p.PartID
WHERE pp.ProductionYear = '2009'
I believe what you are looking for is a joined subclass. In FNH, it will look something like:
public class PartMap : ClassMap<Part>
{
public PartMap()
{
Id(x => x.Id)
JoinedSubClass<ProducedPart>("PartID", sub => {
sub.Map(x => x.Name);
sub.Map(x => x.ProductionYear);
});
}
}
In order have NHibernate cache the results, you will need to have the subclass mapped (and if you didn't map it, you wouldn't be able to get NH to load it in the first place).
Bringing in some context from the FNH groups thread, it will not explicitly be read-only though. In my opinion, making things read-only is not an appropriate thing for NHibernate to manage. This is better controlled by the database and connections (i.e. creating a connection to the database that only has SELECT permissions on the tables/views being accessed). See my answer to a previous SO question about readonly sessions in NHibernate for more of my thoughts on the matter.
The key here is using both the where and mutable elements of the class definition for NHibernate Mappings.
Using Fluent NHibernate, this looks like:
public Part()
{
WithTable("t_Part");
Id(i => i.Id).ColumnName("PartID");
Map(m => m.Name).ColumnName("Part");
SetAttribute("where", "PartID IN ( SELECT pp.PartID FROM t_PartProduction pp WHERE pp.ProductionYear = '2009' ) ");
ReadOnly();
}
No, this is perfectly possible. Look in the NHibernate documentation for the "table per subclass" model of inheritance. It will actually implement this as a LEFT JOIN, so that when you load a Part, it creates an instance of either your Part or your ProducedPart class depending on whether the other row is present. You'll find documentation on nhibernate.info.
I'm not sure you could make ProducedPart read-only doing this though.
I'm assuming from this:
WHERE pp.ProductionYear = '2009'
that you want the subclass only where the production year is 2009, i.e. if when there is a record in t_PartProduction for a different year, you want this Part treated as a plain Part object, not a ProducedPart, then you could consider creating a view definition within your database that is a filtered version of t_PartProduction, then making your subclass join to this view rather than the base table.
I am using: NHibernate, NHibernate.Linq and Fluent NHibernate on SQL Server Express 2008. I am selecting an entity using a predicate on a referenced property (many-one mapping). I have fetch=join, unique=true, lazy-load=false. I enabled the log4net log and when any such query executes it logs two identical SQL queries. Running the query returns one row, and when I attempt to use the IQueryable.Single extension method it throws the exception stating there is more than one row returned. I also tried running the query using the standard IQuery.UniqueResult method with the same result, it ends up logging and actually running the query twice, then throwing an exception stating that there were multiple rows, however running the actual query in management studio returns only one result. When I disable logging I receive the same error.
The entities and mappings are declared as follows (proper access modifiers and member type variance are implied)
class User
{
int ID;
string UserName;
}
class Client
{
int ID;
User User;
Person Person;
Address Address;
}
class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.ID);
Map(x => x.UserName);
}
}
class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Id(x => x.ID);
References(x => x.User).Unique();
...
}
}
Then I invoke a queries such as the following:
ISession s = GetNHibernateSession();
...
var client = s.Linq<Client>().SingleOrDefault(x => x.User.ID = 17);
or
var client = s.Linq<Client>().Where(x => x.User.ID = 17);
or
var client = s.CreateQuery("from Client as c where c.User.ID = 17").UniqueResult<Client>();
In all cases executes two identical queries. When I enable lazy load, the client is again loaded using two queries, however upon accessing a member, such as Person, only one additional query is executed.
Is this possibly a result of Fluent generating an improper mapping? Or SQL Server Express edition not being used properly by NHibernate?
The problem was caused by another mapping I had declared. I had a class inheriting from Client which had an associated mapping. This is what caused NHibernate to query twice. I noticed this because when using Linq() it returned the subclass, not Client itself. This particular instance of inheritance and mapping was a design flaw on my part and was the root of the whole problem!
NHibernate doesn't have any trouble with SQL Express, I've used it fairly extensively. Similarily, it's unlikely Fluent NHibernate is generating invalid mappings in this simple scenario (but not unheard of).
A shot in the dark, but I believe NHibernate reserves the name Id as an identifier name, so when it sees Id in the query it knows to just look at the foreign key instead of the actual joined entity. Perhaps your naming of ID instead of Id is throwing it off?
You could try using the excellent NHibernate profiler for a more detailed view of what is happening. It comes with a 30 day trial license and while in Beta there is a discount on the full license cost