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?
Related
I'm trying to set up easy test data in my Acceptance tests:
public function shouldUseAFakeAccountHolder(AcceptanceTester $I) {
$I->have(AccountHolder::class);
// ...
}
I've copied the example code from the Codeception documentation and modified it with my entity names (as well as fixing the bugs).
<?php
public function _beforeSuite()
{
$factory = $this->getModule('DataFactory');
// let us get EntityManager from Doctrine
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(AccountHolder::class, [
'firstName' => Faker::firstName(),
// Comment out one of the below 'accountRole' lines before running:
// get existing data from the database
'accountRole' => $em->getRepository(AccountRole::class)->find(1),
// create a new row in the database
'accountRole' => 'entity|' . AccountRole::class,
]);
}
The relationship using existing data 'accountRole' => $em->getRepository(AccountRole::class)->find(1) always fails:
[Doctrine\ORM\ORMInvalidArgumentException] A new entity was found through the relationship 'HMRX\CoreBundle\Entity\AccountHolder#accountRole' that was not configured to cascade persist operations for entity: HMRX\CoreBundle\Entity\AccountRole#0000000062481e3f000000009cd58cbd. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'HMRX\CoreBundle\Entity\AccountRole#__toString()' to get a clue.
If I tell it to create a new entry in the related table 'accountRole' => 'entity|' . AccountRole::class, it works, but then it adds rows to the table when it should be using an existing row. All the role types are known beforehand, and a new random role type makes no sense because there's nothing in the code it could match to. Creating a duplicate role works, but again it makes so sense to have a separate role type for each user since roles should be shared by users.
I've had this error before in Unit tests, not Acceptance tests, when not using Faker / FactoryMuffin, and it's been to do with accessing each entity of the relationship with a different instance of EntityManager. As soon as I got both parts using the same instance, it worked. I don't see how to override the native behaviour here though.
It works (at least in Codeception 4.x) by using a callback for the existing relation:
<?php
public function _beforeSuite()
{
$factory = $this->getModule('DataFactory');
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(AccountHolder::class, [
'firstName' => Faker::firstName(),
'accountRole' => function($entity) use ($em) {
$em->getReference(AccountRole::class)->find(1);
},
]);
}
I've found it here: https://github.com/Codeception/Codeception/issues/5134#issuecomment-417453633
Is it possible to distribute a NHibernate-by-Code-Mapping over several classes?
E.g.
public class EntityMap1 : ClassMapping<Entity> {
Id(x => x.Id);
Property(x => x.PropertyOne);
}
public class EntityMap2 : ClassMapping<Entity> {
Property(x => x.PropertyTwo);
}
I tried it but the mapping of PropertyTwo was missing in the generated HBML. Is there some way to achieve this?
I don't believe NHibernate would be able to compile both together to create a singular mapping. If the goal is to use a different set of mappings in one app versus another, you need to simply create two different mappings. If the goal is to have subclasses, there is a SubclassMapping interface you can extend.
Edit:
In looking over my notes, an extension to my answer about creating a different set of mappings would be the case where you have some feature plugged into your app that needs a different (sometimes more, sometimes less involved) mapping. To do this you need to have NHibernate generate them separately and add them to the configuration separately. Using conventions, this creates two separate sets of mappings (which contain some overlapping, but differently mapped, entites) that are plugged into one configuration:
NHibernateConfiguration.BeforeBindMapping += (sender, args) => args.Mapping.autoimport = false;
var pluginMappings = new PluginMapper().Mappings;
foreach (var hbmMapping in pluginMappings)
NHibernateConfiguration.AddDeserializedMapping(hbmMapping, "PluginModel");
var mainAppMappings = new AppMapper().Mappings;
foreach (var hbmMapping in mainAppMappings)
NHibernateConfiguration.AddDeserializedMapping(hbmMapping, "AppModel");
As described in my comment to Fourth's answer the goal was that a plugin can modify the mapping of the main application, i.e. EntityMap1 would reside in the main program and EntityMap2 in the plugin. I could avoid this problem by only keeping EntityMap1 and manually modifying the generated XML.
var domainMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
var oldMappingXml = domainMapping.AsString();
var newMappingXml = ModifyMappings(oldMappingXml);
configuration.AddXmlString(newMappingXml);
oldMappingXml contains the XML generated by the mappings defined in the main application and ModifyMappings adds the changes required by the plugin. This is possible because the changes required by the plugins are well defined and follow the same algorithm for all plugins.
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?
I need to use ClassMaps instead of auto mapping because of legacy database. But I don't see how to tune SharpArch to use them. I tried to remove AutoPersistentModelGenerator and use the following code in the InitializeNHibernateSession method:
var config = NHibernateSession.Init(webSessionStorage,
new[]{"ApplicationConfiguration.Models.dll"});
Fluently.Configure(config)
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<ConfigSchema>();
});
But I always get MappingException - "No persister for: ConfigSchema" when trying to work with the ConfigSchema.
Has anyone tried to do this?
Edit:
ConfigSchema is a part of domain model.
I'm stupid. Fluently.Configure(config) generates a new config for NHibernate. So it will never be used in my scenario. All I was need is to use the following code in the AutoPersistentModelGenerator:
public AutoPersistenceModel Generate()
{
var mappings = new AutoPersistenceModel();
mappings.AddMappingsFromAssembly(typeof(ConfigVersionMap).Assembly);
return mappings;
}
I'm not all that familiar with the S#arp project, but is ConfigSchema a type from you domain model? The generic argument T to AddFromAssemblyOf<T> should be a mapped class from your domain model.
First time rocking it with NHibernate/Fluent so apologies in advance if this is a naive question. I have a set of Models I want to map. When I create my session factory I'm trying to do all mappings at once. I am not using auto-mapping (though I may if what I am trying to do ends up being more painful than it ought to be). The problem I am running into is that it seems only the top map is taking. Given the code snippet below and running a unit test that attempts to save 'bar', it fails and checking the logs I see NHibernate is trying to save a bar entity to the foo table. While I suspect it's my mappings it could be something else that I am simply overlooking.
Code that creates the session factory (note I've also tried separate calls into .Mappings):
Fluently.Configure().Database(MsSqlConfiguration.MsSql2008
.ConnectionString(c => c
.Server(#"localhost\SQLEXPRESS")
.Database("foo")
.Username("foo")
.Password("foo")))
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<FooMap>()
.Conventions.Add(FluentNHibernate.Conventions.Helpers
.Table.Is(x => "foos"));
m.FluentMappings.AddFromAssemblyOf<BarMap>()
.Conventions.Add(FluentNHibernate.Conventions.Helpers
.Table.Is(x => "bars"));
})
.BuildSessionFactory();
Unit test snippet:
using (var session = Data.SessionHelper.SessionFactory.OpenSession()) {
var bar = new Bar();
session.Save(bar);
Assert.NotNull(bar.Id);
}
You're doing it wrong. :)
Firstly, m.FluentMappings.AddFromAssemblyOf<FooMap>() and m.FluentMappings.AddFromAssemblyOf<BarMap>() are doing exactly the same thing (if FooMap and BarMap are in the same assembly). Each one just tells Fluent NHibernate to scan the assembly that contains the generic type; so if both types are in the same assembly, it'll scan it twice.
Secondly, the Conventions call is not scoped to the specific assembly you call it after, it's for the whole set of mappings. So what you're doing is supplying two conventions to set the table name to an explicit value, and the second one is the last one to be applied. What you want to do is use the x parameter (which is the entity type) and create your table name from that.
What you need is something like this:
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<FooMap>()
.Conventions.Add(Table.Is(x => x.Name + "s"));
})
Obviously my implementation is naive, and depending on what your table naming convention is you might want to use a pluraliser (I believe Castle has one, but it shouldn't be hard to find one with google).
You can read up more about conventions on the Fluent NHibernate wiki.
With classmap you specify the table name in the mapping. If not specified, it will be the same as the entity class name.
class FooMap : ClassMap<Foo>
{
public FooMap()
{
Table("foos");
}
}
Conventions apply to all mappings. As you added 2 table name conventions, only 1 will take effect.
Are your FooMap and BarMap in the same assembly? You only need to add each assembly once.
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<FooMap>())
Just want to share this will automatically add plural name either s, es, ies based on the end alphabet. Not so sure about grammar and some exception but this seems ok for me. Any exception use Table("foos") like #Lachlan Roche said. E.g. Customer class will have Customers table and Category class will have Categories table.
Modified #James Gregory answer:
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<FooMap>()
.Conventions.Add(Table.Is(x => GetPluralName(x.Name));
})
public static string GetPluralName(string oldName)
{
// This is the very simple. Just ignore exception like days, boys, photos and other specific nouns.
if (oldName.EndsWith("y"))
return oldName.Remove(oldName.Length - 1) + "ies";
else if (oldName.EndsWith("s") || oldName.EndsWith("e") || oldName.EndsWith("h") || oldName.EndsWith("z") || oldName.EndsWith("o")) // Sibilant consonant or "o"
return oldName + "es";
return oldName + "s";
}