Using Fluent NHibernate's AutoPersistenceModel but with eager loading in a single object - nhibernate

I'm using Fluent NHibernate in order to auto map my entities.
This is the code I'm using for the auto mapping:
new AutoPersistenceModel()
.AddEntityAssembly(Assembly.GetAssembly(typeof(Entity)))
.Where(type => type.Namespace.Contains("Domain") && type.BaseType != null && type.BaseType.Name.StartsWith("DomainEntity") && type.BaseType.IsGenericType == true)
.WithSetup(s => s.IsBaseType = (type => type.Name.StartsWith("DomainEntity") && type.IsGenericType == true))
.ConventionDiscovery.Add(
ConventionBuilder.Id.Always(x => x.GeneratedBy.Increment())
);
This works just fine. But now I need to have Eager Loading in one single object of my domain. Found this answer. But when I add the line .ForTypesThatDeriveFrom<IEagerLoading>(map => map.Not.LazyLoad()) to the code and run it I get the following exception:
Error while trying to build the Mapping Document for IEagerLoading
Notice that I'm using an interface (IEagerLoading) to mark the objects that I want eager load.
Can anyone help how to do this? Remember that I want to keep the auto mapping functionality.
Thanks

The problem you're hitting is that ForTypesThatDeriveFrom<T> is a bit misleadingly named, and that it really means ForMappingsOf<T>, so it's trying to find a ClassMap<IEagerLoading> which obviously doesn't exist.
I believe you should be able to handle this with a custom IClassConvention. This is off the top of my head, but should work:
public class EagerLoadingConvention : IClassConvention
{
public bool Accept(IClassMap target)
{
return GetType().GetInterfaces().Contains(typeof(IEagerLoading));
}
public void Apply(IClassMap target)
{
target.Not.LazyLoad();
}
}

Related

Nhibernate Clear Cache for a Specific Region

I am trying to manually clear the level 2 cache for a specific region. I found the method posted in answer to this question. While this is working to clear my entities, for some reason the querycache is not getting cleared. This results in a separate query for each entity the next time the entities are retrieved from the database. If does work when I call sessionFactory.EvictQueries() without any parameters. It is only not working when I am passing in a specific region name. Any ideas as to what is going wrong?
Code is from the above link:
private void ClearRegion(string regionName)
{
_sessionFactory.EvictQueries(regionName);
foreach (var collectionMetaData in _sessionFactory.GetAllCollectionMetadata().Values)
{
var collectionPersister = collectionMetaData as NHibernate.Persister.Collection.ICollectionPersister;
if (collectionPersister != null)
{
if ((collectionPersister.Cache != null) && (collectionPersister.Cache.RegionName == regionName))
{
_sessionFactory.EvictCollection(collectionPersister.Role);
}
}
}
foreach (var classMetaData in _sessionFactory.GetAllClassMetadata().Values)
{
var entityPersister = classMetaData as NHibernate.Persister.Entity.IEntityPersister;
if (entityPersister != null)
{
if ((entityPersister.Cache != null) && (entityPersister.Cache.RegionName == regionName))
{
_sessionFactory.EvictEntity(entityPersister.EntityName);
}
}
}
}
Caching is working and verified using NHProfiler.
Ok, so I figured out my issue. I did not realize that it is necessary to specify a cache region when querying the data, aside from specifying it in the entity mapping. After adding .CacheRegion("regionName") to my queries everything works. By not adding the region when querying, it was going into the query cache without a region name. That is why it worked when I called.EvictQueries() without a region name parameter.
To sum it up, it is necessary to add the region name when mapping the entities (.Region("regionName") when using Fluent) and when querying with isession using .CacheRegion("regionName").
Thank you for you responses.

Raven Index creation gets compilation error CS1977 when using FindLastIndex

I'm trying to write an index that looks at our logs to find entities that are unavailable due to undergoing a part of the process but not a second part of the process which then makes them available. Our import process happens first, then our clean process. Entities that have gone through our clean process are then considered available. I want to essentially find out those entities that have undergone the import process (therefore resetting them) after the last time the cleaning occurred.
I came up with this potential map:
AddMap<EntityLog>(docs => docs.Where(doc => doc.Details != null)
.Where(doc => doc.Details.Any(d => d.QueueMessage != null && d.QueueMessage.JobsToDo.Contains(JobType.Import)
&& d.Finished != null))
.Where(doc => doc.Details.Any(d => d.QueueMessage != null && d.QueueMessage.JobsToDo.Contains(JobType.Clean)
&& d.Finished != null))
.Where(doc => doc.Details.FindLastIndex(d => d.QueueMessage.JobsToDo.Contains(JobType.Clean) && d.Finished != null) <
doc.Details.FindLastIndex(d => d.QueueMessage.JobsToDo.Contains(JobType.Import) && d.Finished != null))
.Select(found => new
{
found.EntityID,
User = found.Details.Select(d => d.User)
}));
It compiles in VS, but when I try to make this index, I get the following error:
CS1977 "Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type"
From some selective commenting, it seems to be the line where I'm using FindLastIndex. And from research, I know that this compiler error comes in when you're trying to do stuff with untyped objects that you're not supposed to. From looking at the output Raven-made code, it uses dynamic a lot, so I'm guessing its something related to that. I did try using this as an alternative to that line:
.Where(doc => Array.FindLastIndex(doc.Details.ToArray(), d => d.QueueMessage.JobsToDo.Contains(JobType.Clean) && d.Finished != null) <
Array.FindLastIndex(doc.Details.ToArray(), d => d.QueueMessage.JobsToDo.Contains(JobType.Import) && d.Finished != null))
But no joy, I still get the same error message.
How do I get this code to work? Or do I need to take a completely different approach?
Any help appreciated.
FindLastIndex is a method on List, but we don't know what the type is on the server side, so that isn't available.
Use the extension method on IEnumerable, instead.
Just a create a get only property on your EntityLog that uses the FindLastIndex then the results of that information is what will be serialized to the database and easy to search upon.
This is a rather common pattern I follow when working with Lists.
public class Foo {
public List<Phones> Phones { get;set; }
public Phone PrimaryPhone {
get { return Phones.SingleOrDefault(x=> x.Primary == true)) }
}
}
Note, if EntityLog is an already existing document collection, you will need to Load each log and save it back to the database.

How to resolve a Collection of Types from within the IoC Container

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;
}
}

How do you work with detached QueryOver instances?

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?

NHibernate Search Index poco object throws TransientObjectException

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.