Is it possible to distribute NHibernate-by-Code-Mappings over several classes? - nhibernate

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.

Related

EF Core include related ids but not related entities

Before I go creating my own SQL scripts by hand for this, I have a scenario where I want to get the ids of a foreign key, but not the entirety of the foreign entities, using EF Core.
Right now, I'm getting the ids manually by looping through the related entities and extracting the ids one at a time, like so:
List<int> ClientIds = new List<int>();
for (var i = 0; i < Clients.length; i++){
ClientIds.add(Clients.ElementAt(i).Id);
}
To my understanding, this will either cause data returns much larger than needed (my entity + every related entity) or a completely separate query to be run for each related entity I access, which obviously I don't want to do if I can avoid it.
Is there a straightforward way to accomplish this in EF Core, or do I need to head over the SQL side and handle it myself?
Model:
public class UserViewModel {
public UserViewModel(UserModel userModel){
ClientIds = new List<int>();
for (var i = 0; i < UserModel.Clients.length; i++){
ClientIds.add(Clients.ElementAt(i).Id);
}
//...all the other class asignments
}
public IEnumerable<int> ClientIds {get;set;}
//...all the other irrelevant properties
}
Basically, I need my front-end to know which Client to ask for later.
It looks like you are trying to query this from within the parent entity. I.e.
public class Parent
{
public virtual ICollection<Client> Clients { get; set; }
public void SomeMethod()
{
// ...
List<int> ClientIds = new List<int>();
for (var i = 0; i < Clients.length; i++)
{
ClientIds.add(Clients.ElementAt(i).Id);
}
// ...
}
}
This is not ideal because unless your Clients were eager loaded when the Parent was loaded, this would trigger a lazy load to load all of the Clients data when all you want is the IDs. Still, it's not terrible as it would only result in one DB call to load the clients.
If they are already loaded, there is a more succinct way to get the IDs:
List<int> ClientIds = Clients.Select(x => x.Id).ToList();
Otherwise, if you have business logic involving the Parent and Clients where-by you want to be more selective about when and how the data is loaded, it is better to leave the entity definition to just represent the data state and basic rules/logic about the data, and move selective business logic outside of the entity into a business logic container that scopes the DbContext and queries against the entities to fetch what it needs.
For instance, if the calling code went and did this:
var parent = _context.Parents.Single(x => x.ParentId == parentId);
parent.SomeMethod(); // which resulted in checking the Client IDs...
The simplest way to avoid the extra DB call is to ensure the related entities are eager loaded.
var parent = _context.Parents
.Include(x => x.Clients)
.Single(x => x.ParentId == parentId);
parent.SomeMethod(); // which resulted in checking the Client IDs...
The problem with this approach is that it will still load all details about all of the Clients, and you end up in a situation where you end up defaulting to eager loading everything all of the time because the code might call something like that SomeMethod() which expects to find related entity details. This is the use-case for leveraging lazy loading, but that does have the performance overheads of the ad-hoc DB hits and ensuring that the entity's DbContext is always available to perform the read if necessary.
Instead, if you move the logic out of the entity and into the caller or another container that can take the relevant details, so that this caller projects down the data it will need from the entities in an efficient query:
var parentDetails = _context.Parents
.Where(x => x.ParentId == parentId)
.Select(x => new
{
x.ParentId,
// other details from parent or related entities...
ClientIds = x.Clients.Select(c => c.Id).ToList()
}).Single();
// Do logic that SomeMethod() would have done here, or pass these
// loaded details to a method / service to do the work rather than
// embedding it in the Entity.
This doesn't load a Parent entity, but rather executes a query to load just the details about the parent and related entities that we need. In this example it is projected into an anonymous type to hold the information we can later consume, but if you are querying the data to send to a view then you can project it directly into a view model or DTO class to serialize and send.

How to link to a child site-map file from a parent site map in ASP.NET MVC4 using MVCSitemapProvider?

I am using MVCSitemapProvider by Maarten Balliauw with Ninject DI in MVC4. Being a large-scale web app, enumerating over the records to generate the sitemap xml accounts for 70% of the page load time. For that purpose, I went for using new sitemap files for each level-n dynamic node provider.
<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0" xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd">
<mvcSiteMapNode title="$resources:SiteMapLocalizations,HomeTitle" description="$resources:SiteMapLocalizations,HomeDescription" controller="Controller1" action="Home" changeFrequency="Always" updatePriority="Normal" metaRobotsValues="index follow noodp noydir"><mvcSiteMapNode title="$resources:SiteMapLocalizations,AboutTitle" controller="ConsumerWeb" action="Aboutus"/>
<mvcSiteMapNode title="Sitemap" controller="Consumer1" action="SiteMap"/><mvcSiteMapNode title=" " action="Action3" controller="Consumer2" dynamicNodeProvider="Comp.Controller.Utility.NinjectModules.PeopleBySpecDynamicNodeProvider, Comp.Controller.Utility" />
<mvcSiteMapNode title="" siteMapFile="~/Mvc2.sitemap"/>
</mvcSiteMapNode>
</mvcSiteMap>
But, it doesn't seem to work. For localhost:XXXX/sitemap.xml, the child nodes from Mvc2.sitemap don't seem to load.
siteMapFile is not a valid XML attribute in MvcSiteMapProvider (although you could use it as a custom attribute), so I am not sure what guide you are following to do this. But, the bottom line is there is no feature that loads "child sitemap files", and even if there was, it wouldn't help with your issue because all of the nodes are loaded into memory at once. Realistically on an average server there is an upper limit of around 10,000 - 15,000 nodes.
The problem that you describe is a known issue. There are some tips available in issue #258 that may or may not help.
We are working a new XML sitemap implementation that will allow you to connect the XML sitemap directly to your data source, which can be used to circumvent this problem (at least as far as the XML sitemap is concerned). This implementation is stream-based and has paging that can be tied directly to the data source, and will seamlessly page over multiple tables, so it is very efficient. However, although there is a working prototype, it is still some time off from being made into a release.
If you need it sooner rather than later, you are welcome to grab the prototype from this branch.
You will need some code to wire it into your application (this is subject to change for the official release). I have created a demo project here.
Application_Start
var registrar = new MvcSiteMapProvider.Web.Routing.XmlSitemapFeedRouteRegistrar();
registrar.RegisterRoutes(RouteTable.Routes, "XmlSitemap2");
XmlSitemap2Controller
using MvcSiteMapProvider.IO;
using MvcSiteMapProvider.Web.Mvc;
using MvcSiteMapProvider.Xml.Sitemap.Configuration;
using System.Web.Mvc;
public class XmlSitemap2Controller : Controller
{
private readonly IXmlSitemapFeedResultFactory xmlSitemapFeedResultFactory;
public XmlSitemap2Controller()
{
var builder = new XmlSitemapFeedStrategyBuilder();
var xmlSitemapFeedStrategy = builder
.SetupXmlSitemapProviderScan(scan => scan.IncludeAssembly(this.GetType().Assembly))
.AddNamedFeed("default", feed => feed.WithMaximumPageSize(5000).WithContent(content => content.Image().Video()))
.Create();
var outputCompressor = new HttpResponseStreamCompressor();
this.xmlSitemapFeedResultFactory = new XmlSitemapFeedResultFactory(xmlSitemapFeedStrategy, outputCompressor);
}
public ActionResult Index(int page = 0, string feedName = "")
{
var name = string.IsNullOrEmpty(feedName) ? "default" : feedName;
return this.xmlSitemapFeedResultFactory.Create(page, name);
}
}
IXmlSiteMapProvider
And you will need 1 or more IXmlSitemapProvider implementations. For convenience, there is a base class XmlSiteMapProviderBase. These are similar to creating controllers in MVC.
using MvcSiteMapProvider.Xml.Sitemap;
using MvcSiteMapProvider.Xml.Sitemap.Specialized;
using System;
using System.Linq;
public class CategoriesXmlSitemapProvider : XmlSitemapProviderBase, IDisposable
{
private EntityFramework.MyEntityContext db = new EntityFramework.MyEntityContext();
// This is optional. Don't override it if you don't want to use last modified date.
public override DateTime GetLastModifiedDate(string feedName, int skip, int take)
{
// Get the latest date in the specified page
return db.Category.OrderBy(x => x.Id).Skip(skip).Take(take).Max(c => c.LastUpdated);
}
public override int GetTotalRecordCount(string feedName)
{
// Get the total record count for all pages
return db.Category.Count();
}
public override void GetUrlEntries(IUrlEntryHelper helper)
{
// Do not call ToList() on the query. The idea is that we want to force
// EntityFramework to use a DataReader rather than loading all of the data
// at once into RAM.
var categories = db.Category
.OrderBy(x => x.Id)
.Skip(helper.Skip)
.Take(helper.Take);
foreach (var category in categories)
{
var entry = helper.BuildUrlEntry(string.Format("~/Category/{0}", category.Id))
.WithLastModifiedDate(category.LastUpdated)
.WithChangeFrequency(MvcSiteMapProvider.ChangeFrequency.Daily)
.AddContent(content => content.Image(string.Format("~/images/category-image-{0}.jpg", category.Id)).WithCaption(category.Name));
helper.SendUrlEntry(entry);
}
}
public void Dispose()
{
db.Dispose();
}
}
Note that there is currently not an IXmlSiteMapProvider implementation that reads the nodes from the default (or any) SiteMap, but creating one is similar to what is shown above, except you would query the SiteMap for nodes instead of a database for records.
Alternatively, you could use a 3rd party XML sitemap generator. Although, nearly all of them are set up in a non-scalable way for large sites, and most leave it up to you to handle the paging. If they aren't streaming the nodes, it will not realistically scale to more than a few thousand URLs.
The other detail you might need to take care of is to use the forcing a match technique to reduce the total number of nodes in the SiteMap. If you are using the Menu and/or SiteMap HTML helpers, you will need to leave all of your high-level nodes alone. But any node that does not appear in either is a good candidate for this. Realistically, nearly any data-driven site can be reduced to a few dozen nodes using this technique, but keep in mind every node that is forced to match multiple routes in the SiteMap means that individual URL entries will need to be added in the XML sitemap.

NCommon + Fluent NHibernate + Multiple Databases?

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?

Fluent Nhibernate - ClassMaps in multiple, separate assemblies

Consider the following Fluent configuration;
FluentConfiguration config = Fluently.Configure();
config.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2008.ConnectionString(ConfigurationManager.ConnectionStrings[dbKey].ConnectionString));
// MemberMap is in the same assembly as this class, contains
// maps for member and role entities as part of a default
// membership service provider
MappingAssemblies.Add(typeof(MemberMap).Assembly);
foreach (Assembly mappingAssembly in MappingAssemblies)
{
// For each assembly that has been added to MappingAssemblies
// we add to the current mapping configuration. This allows
// me to drop this helper into any solution and it provide
// standardized membership abilities AS WELL AS manage
// the solutions unique ClassMaps
config.Mappings(m =>
m.FluentMappings.AddFromAssembly(mappingAssembly)
);
}
if (exportSchema)
{
config.ExposeConfiguration(cfg =>
{
new SchemaExport(cfg)
.Create(true, true);
}
);
}
_sessionFactory = config.BuildSessionFactory();
This logic is held within a static class I call from within my Global.asax on application startup. The startup configuration will look something similar to;
Database.MappingAssemblies.Add(typeof(PageContentMap).Assembly);
// This is the method detailed above
Database.FluentConfigureSessionFactory("MySolutionsDb", true);
So the idea is that I have packaged my Member and Role entity objects into the same assembly as the Database helper object so that any solution I care to create can instantly gain my standardized membership abilities as well as being able to simply create its own solution specific ClassMaps and add them to the configuration object.
The issue is, the familiar call to;
config.Mappings(m =>
m.FluentMappings.AddFromAssembly(mappingAssembly)
);
only seems to be able to deal with a single assembly. Doesn't matter what is added to the list, only the last assembly added will be mapped. As an alternative to the above, I have tried holding a reference to MappingConfiguration (which is what 'm' stands for in the config.Mappings(m => ) ) but this did not work either. It is obvious that such a call to m.FluentMappings.AddFromAssembly or indeed any of the FluentMappings.Add methods will overwrite what is previously there but surely there is a way of getting this done ? It doesn't seem like that much of a 'weird' requirement.
Old question, but I managed to solve it after looking at this, so I'll try to answer it. This is how I did (the .ForEach() is an extension from NHibernate.Linq):
config.Mappings(m => MappingAssemblies.ForEach(a => m.FluentMappings.AddFromAssembly(a)))
I had to do it for auto-mapped stuff as well, and there the syntax is a bit different. I have an interface that marks all classes I want to automap:
config.Mappings(m =>
m.AutoMappings.Add(AutoMap.Assemblies(MappingAssemblies.ToArray())
.Where(x => x.GetInterfaces().Contains(typeof(IAutoMappedEntity)))))
Also, I don't have a "MappingAssemblies" that I set manually, I took the lazy approach of just including all my assemblies, so my config looks like this (using SQLite, this is from a test project):
var MappingAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => a.FullName.StartsWith("MyCompanyName."));
Configuration configuration;
var sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory())
// This adds fluent mappings
.Mappings(m => MappingAssemblies.ForEach(a => m.FluentMappings.AddFromAssembly(a)))
// This adds automapped classes using specific configuration
.Mappings(m => m.AutoMappings.Add(AutoMap.Assemblies(new MyAutomapConfiguration(), MappingAssemblies)))
// This adds automapped classes that just use my magic interface
.Mappings(m => m.AutoMappings.Add(AutoMap.Assemblies(MappingAssemblies.ToArray()).Where(x => x.GetInterfaces().Contains(typeof(IAutoMappedEntity)))))
.ExposeConfiguration(cfg => configuration = cfg)
.BuildSessionFactory();

How to configure multiple mappings using FluentHibernate?

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