I have the following mapping override on one side of the relationship:
public void Override(AutoMapping<ItemAsmtDetailDh> mapping)
{
mapping.HasMany<WAsmtDetail>(x => x.WAsmtDetails).Inverse().AsBag().Cascade.AllDeleteOrphan().Access.PascalCaseField(Prefix.Underscore).Not.LazyLoad().Fetch.Join();
}
and on the other side of the relationship I have:
public void Override(AutoMapping<WAsmtDetail> mapping)
{
mapping.References<ItemAsmtDetailDh>(x => x.ItemAsmtDetailDh).Not.Nullable().Not.LazyLoad().Fetch.Join();
}
When I use the ShowSql option I see that it's still issuing separate select statements for the WAsmtDetails giving me the dreaded "n + 1 selects" problem. Why is ".Not.LazyLoad().Fetch.Join()" being ignored?
Note: I'm using Fluent NHibernate version 1.1, not version 2.1, because of a bug in the newer version. (See my answer for this question for bug details.) I'm using NHibernate version 2.1.2.4000.
You are most likely loading the data in a way that isn't affected by Fetch.Join() in the mapping (like HQL or Linq). From the NHibernate documentation:
The fetch strategy defined in the mapping document affects:
retrieval via Get() or Load()
retrieval that happens implicitly when an association is navigated
ICriteria queries
HQL queries if subselect fetching is used
Related
I have a table (UncommittedVideoFile) that has a strict one-to-one relationship with a different table (VideoOrImageAsset). When I load an UncommittedVideoFile, I want to load the VideoOrImageAsset in the same query via a JOIN. (Inner or left outer).
Apparently it's possible to specify fetch-by-join when you use xml mapping: https://ayende.com/blog/3960/nhibernate-mapping-one-to-one
It's also possible if you are using Fluent NHibernate: https://stackoverflow.com/a/15694723/25216
But using MappingByCode, is is possible to set a Fetch option? I can't see one anywhere.
Here is the code that I currently have. The IOneToOneMapper interface does not have a Fetch method.
classMapper.OneToOne(
f => f.VideoOrImageAsset,
oneToOneMapper =>
{
oneToOneMapper.Constrained(true);
oneToOneMapper.ForeignKey("Id");
oneToOneMapper.Lazy(LazyRelation.NoLazy);
}
);
It's known missing mapping-by-code feature GH-2139. And it's already implemented in 5.3 (not yet released, but pretty close)
For now you can use pre-release version of 5.3 from https://www.myget.org/gallery/nhibernate (also check notes how to use it here)
I doubt this is just specific to NHibernate. But I have code as follows....
public class ClientController : ApiController
{
// GET /api/<controller>
public IQueryable<Api.Client> Get()
{
return Repositories.Clients.Query().Select(c => Mapper.Map<Client, Api.Client>(c));
}
I basically want to Query the database using the Odata criteria.... get the relevant 'Client' objects, and the convert them to the DTO 'Api.Client'.
But... the code as is, doesn't work. Because NHibernate doesn't know what to do the with the Mapper.... It really wants the query to come before the .Select. But I'm not sure I can get the Odata Query first?
It will work if I do
return Repositories.Clients.Query().Select(c => Mapper.Map<Client, Api.Client>(c)).ToList().AsQueryable();
But that's a bit sucky as you have to get ALL the clients from the database to do the OData query on.
Is there anyway to get the "Select" to happen after the OData query? Or another way to approach this?
I did not test it yet but the Open Source project NHibernate.OData could be useful for you.
The problem is that you are trying to execute C# code (Mapper.Map) inside the NH call (that is translated to SQL)
You'd have to map Api.Client manually, or create a Mapper implementation that returns an Expression<Func<Client, Api.Client>> and pass it directly as a parameter to Select().
Even with that, I'm not sure if NHibernate will translate it. But you can try.
AutoMapper supports this scenario with the Queryable Extensions
public IQueryable<Api.Client> Get() {
return Repositories.Clients.Query().Select(c => Mapper.Map<Client, Api.Client>(c));
}
becomes
public IQueryable<Api.Client> Get() {
return Repositories.Clients.Query().ProjectTo<Api.Client>(mapper.ConfigurationProvider);
}
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.
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 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