This NHibernate blog entry notes how detached QueryOver queries (analogous to DetachedCriteria) can be created (using QueryOver.Of<T>()). However, looking this over, it doesn't look analogous to me at all.
With DetachedCriteria, I would create my instance and set it up however I need, and afterwards call GetExecutableCriteria() to then assign the session and execute the query. With the "detached" QueryOver, most of the API is unavailable (ie, to add restrictions, joins, ordering, etc...) until I call GetExecutableQueryOver, which requires takes an ISession or IStatelessSession, at which point you are no longer disconnected.
How do you work with detached QueryOver instances?
EDIT:
Actual problem was related to how I'm storing the detached QueryOver instance:
public class CriteriaQuery<T>
{
internal protected QueryOver<T> _QueryOver { get; set; }
public CriteriaQuery()
{
_QueryOver = QueryOver.Of<T>();
}
// Snip
}
It should be a QueryOver<T, T>.
I'm using NHibernate 3.1.0.4000. The following code compiles successfully:
Employee salesRepAlias = null;
var query = QueryOver.Of<Customer>()
.JoinAlias(x => x.SalesRep, () => salesRepAlias)
.Where(x => x.LastName == "Smith")
.Where(() => salesRepAlias.Office.Id == 23)
.OrderBy(x => x.LastName).Asc
.ThenBy(x => x.FirstName).Asc;
return query.GetExecutableQueryOver(session)
.List();
This illustrates using restrictions, joins, and ordering on a detached QueryOver just like you would with a regular one.
Could you please post the code that demonstrates the API features that are unavailable?
Related
I'm using by code mappings and trying to map a manytomany. This works fine but I need OrderBy for the child collection items. I noticed this has been omitted (it does exist in the HBM mappings). e.g.
public class Mapping : EntityMapper<Category>
{
public Mapping()
{
Set(x => x.Items, m =>
{
m.Table("ItemCategories");
m.Key(k => k.Column("CategoryId"));
m.Inverse(true);
m.Cascade(Cascade.None);
}, col => col.ManyToMany(m =>
{
m.Columns(x => x.Name("ItemId"));
//m.OrderBy("Score desc"); // missing in Nh4.x?
}));
}
}
Is there a workaround for this? I tried following the suggestion in this article whereby I can set the property before the session factory is built but it has no effect. e.g.
cfg.GetCollectionMapping(typeof(Category).FullName + ".Items").ManyToManyOrdering = "Score desc";
cfg.BuildSessionFactory();
Am I doing something wrong or is OrderBy on manytomany not supported in Nh4?
Also, is it possible to restrict the maximum number of items retrieved in the collection?
Replaced the many to many with one to many and introduced an entity that represents the relationship (followed advice from this article).
This has the upside of allowing you to map the order-by column as well as other columns, and also solved the issue of restricting the number of items in the collection by using the one-to-many's Where() and Filter() clauses.
We're using MvvmCross in our app, and using the MvxSimpleIoCContainer
In the app startup, we register all of our Migrations.
it's easy do do since all migrations inherit from IMigration
typeof (IMigration)
.Assembly
.CreatableTypes()
.Inherits<IMigration>()
.AsTypes()
.RegisterAsLazySingleton();
After the migrations are registered, we need to run them consecutively, and therefore the MigrationRunner looks a little something like this.
Mvx.Resolve<IMigrationRunner>().RunAll(SystemRole.Client, new List<IMigration>
{
Mvx.IocConstruct<Migration001>(),
Mvx.IocConstruct<Migration002>()
});
as you can see, I'm explicitely constructing each Migration using Mvx. This get's tedious and is prone to mistakes when a bunch of migrations end up in the app.
What I'd prefer to be able to do is resolve the entire collection in one fell swoop, and not have to touch it every time I create a new Migration.
Is there a way to do this via MvvmCross?
Pseudo Code
Mvx.Resolve<IMigrationRunner>()
.RunAll(SystemRole.Client, Mvx.ResolveAll<IMigration>());
I would use LINQ to get the list of types. Unfortunately there's no way to get a list of registered types, so you'll have to enumerate the types again like you do for registration. You can even sort by type name. Now that you have a list of types, you can create a new list of instantiated/resolved types to pass into RunAll(). Something like:
var migrationTypes = typeof (IMigration)
.Assembly
.CreatableTypes()
.Inherits<IMigration>()
.AsTypes()
.OrderBy(t => t.Name)
.ToList();
Mvx.Resolve<IMigrationRunner>()
.RunAll(SystemRole.Client,
migrationTypes.Select(t => Mvx.Resolve(t)).ToList());
This is "browser" code, so no guarantees, but you get the gist.
Ok, so reflection is the answer to this problem for now, and eventually, I'd like to either extend our custom MvxServiceLocator : IServiceLocator to include something like
public IEnumerable<object> GetAllInstances(Type serviceType){...}
but for now I've just got a RunMigrations() method in the app
private void RunMigrations()
{
var migrationType = typeof (IMigration); // IMigration is in a separate assembly
var migrations = GetType().Assembly
.GetTypes()
.Where(
t => migrationType.IsAssignableFrom(t) && !t.IsAbstract)
.OrderBy(t => t.Name)
.Select(m => _serviceLocator.GetInstance(m) as IMigration)
.ToList();
var migrationRunner = new MigrationRunner(Mvx.Resolve<IDbProvider>());
migrationRunner.RunAll(SystemRole.Client, migrations);
}
where _serviceLocator.GetInstance(m) just lives in our custom MvxServiceLocator
public object GetInstance(Type serviceType)
{
return _ioCProvider.Resolve(serviceType);
}
Edit: here's how I extended our service locator wrapper.
public class MvxServiceLocator : IServiceLocator
{
private readonly IMvxIoCProvider _ioCProvider;
public MvxServiceLocator(IMvxIoCProvider ioCProvider)
{
_ioCProvider = ioCProvider;
}
public IEnumerable<TService> GetAllInstances<TService>()
{
var serviceType = typeof(TService);
var registrations = GetType().Assembly
.GetTypes()
.Where(
t => serviceType.IsAssignableFrom(t) && !t.IsAbstract)
.Select(m => (TService)_ioCProvider.Resolve(m));
return registrations;
}
}
I've got a bit of code that tries to access same association path twice and they're really same aliases but because I'm using query objects, I have them in two different places and I am not really sure how to get the alias.
May be some code can clear the confusion:
var privateBlogQuery = new BlogQuery()
.ByUser(1)
.RemoveHidden()
.FetchUserDetails();
//<-------- In Blog Query object class: ------>
/// Gets all the private blogs the user has permissions to view
public BlogQuery ByUser(int userId)
{
var authorisedUsers = null;
this.Left.JoinQueryOver(r => r.AuthorisedUsers, () => authorisedUsers)
.Where(r => r.User.Id == userId);
return this;
}
/// Removes all private blogs user has marked to be hidden
public BlogQuery RemoveHidden()
{
var authorisedUsers = null;
this.Left.JoinQueryOver(r => r.AuthorisedUsers, () => authorisedUsers)
.Where(r => !r.IsHidden);
return this;
}
/// Loads up details of all users who have permission
/// to view the private blog
public BlogQuery FetchUserDetails()
{
var users = null;
var authorisedUsers = null;
this.Left.JoinQueryOver(r => r.AuthorisedUsers, () => authorisedUsers)
.Left.JoinQueryOver(r => r.User, () => users);
return this;
}
There are times when I'm using all 3 criteria individually and the sql generated is precisely what I need and everything is nice and dandy as long as they are used separately.
Now I need to use them all together, at the same time and nhibernate throws an exception duplicate alias and I changed up the alias on these three functions but then I am greeted with the duplicate association path exeception.
A bit of googling and I learnt that it is a bug in hibernate and I also found a few workarounds on this bug
Trouble is I am using Query objects and hence Query over and I am not really sure how to get the association path / alias here.
So how do I go about this please?
make authorisedUsers a membervariable of BlogQuery and use a marker/flag to know if ByUser and RemoveHidden should do the Join
use JoinAlias
example
AuthorisedUser authorisedUser;
bool authorisedUsersJoined;
public BlogQuery RemoveHidden()
{
if (!authorisedUsersJoined)
this.Left.JoinAlias(r => r.AuthorisedUsers, () => authorisedUser);
this.Where(() => !authorisedUser.IsHidden);
return this;
}
FetchUserDetails is mutual exclusive with the other two because filtering on an association prevents NH from initializing the association. You'll need to subquery with the filter and query the resulting Ids and initialize then.
/// Loads up details of all users who have permission
/// to view the private blog
public BlogQuery FetchUserDetails()
{
this.Query = QueryOver.Of<Blog>()
.WhereRestrictionOn(b => b.Id).IsIn(this.Query.Select(b => b.Id))
.Fetch(r => r.AuthorisedUsers).Eager
.ThenFetch(au => au.User).Eager;
return this;
}
I'm trying to wire NCommon and NH to multiple databases via the guidance at http://codeinsanity.com (see 'Configuring NCommon for multiple database support') and it works via the fluent approach suggested, below:
var configuration = NCommon.Configure.Using(adapter).ConfigureState<DefaultStateConfiguration>();
configuration.ConfigureData<NHConfiguration>(config => config.WithSessionFactory(() => _sessionFactories[0]).WithSessionFactory(() => _sessionFactories[1]));
This works as expected but as you can see the sessionFactories are hardcoded. What I'd really like to do is something like this:
foreach(ISessionFactory sessionFactory in _sessionFactories)
{
configuration.ConfigureData<NHConfiguration>(config => config.WithSessionFactory(() => sessionFactory));
}
But this throws the following exception:
Component NCommon.Data.NHibernate.NHUnitOfWorkFactory could not be registered. There is already a component with that name. Did you want to modify the existing component instead? If not, make sure you specify a unique name.
My hope is there's a proper way to wire-up n-SessionFactories without hardcoding them - but I'm just not seeing a solution. Any advice?
I thought I got this to work by delegating the SessionFactory piece to a method, as below:
configuration.ConfigureData<NHConfiguration>(config => ConfigureSessionFactories(config));
private void ConfigureSessionFactories(NHConfiguration configuration)
{
foreach (ISessionFactory sessionFactory in _sessionFactories)
{
configuration.WithSessionFactory(() => sessionFactory);
}
}
However, that only appears to be a solution. Say you have multiple session factories, you can use all of them to query, but only the last-added session factory will hit the database and return results. Queries against the other session factories will never hit the actual database and will return zero entities.
The only way I've been able to get this to work is:
configuration.ConfigureData<NHConfiguration>(config => config.WithSessionFactory(() => _sessionFactories[0]).WithSessionFactory(() => _sessionFactories[1]));
I'd rather not hard-code that though.. I'd much rather iterate over a loop of n-session factories... does anyone have an idea of how to accomplish this?
When calling Index method on FullTextSession with plain poco object throws the error below, works fine with proxied object.
Stacktrace:
[TransientObjectException: the instance was not associated with this session]
NHibernate.Impl.SessionImpl.GetIdentifier(Object obj) +500
I'm trying to squeeze the performance out of the nhibernate select method I've got the following code:
public virtual IList<T> LoadSearch()
{
return Adapater.Session.QueryOver<T>()
.SelectList(e =>
{
e.Select(x => x.Id);
e.Select(x => x.Title);
e.Select(x => x.Description);
return e;
}).List<object[]>()
.Select(props => new T
{
Id = (Guid)props[0],
Title = (string)props[1],
Description = (string)props[2]
}).ToList();
}
Is there way to return a proxied result? or some how adapt the list to a proxied list?
I think you can only index objects that are associated with a session, i.e. proxied entities.
The plain POCOs you are returning didn't come from NH - so aren't associated with a NH session.
You could try using ISession.Lock(instance, NHibernate.LockMode.None); on each entity to associate it with the session, but I really don't know if that'd work.