Fluent NHibernate: do not generate any mapping for class during SchemaExport - fluent-nhibernate

I've got an application talking to two databases, and it runs fine, and my tests pass, except when it tries to export the current DB schema, it outputs everything for both DBs in one block, and when it tries to validate the object model / DB schema line up, it tries to look in one database for everything, when one class (which is external data, hence the foreign DB) should not be mapped. I provided a mapping override for the class so that NH can correctly load/use data from the foreign DB (it's read-only), but now when I export my schema, it tries to make that table.
I tried IgnoreBase(typeof(Unit)) but this has no effect (whereas IgnoreBase(typeof(Entity)) does work correctly). I have decorated the custom repository methods with [SessionFactory(DataGlobals.FOREIGN_DB_FACTORY_KEY)] where that simply defines a constant string to use as a key for the SessionFactory, but I'm sure if there's something I need to decorate the class (Unit) with or pass a different parameter to SchemaExport ...
public void CanGenerateDatabaseSchema(){
var session = NHibernateSession.GetDefaultSessionFactory().OpenSession();
using (TextWriter stringWriter = new StreamWriter("../../../../db/schema/UnitTestGeneratedSchema.sql"))
{
new SchemaExport(configuration).Execute(true, false, false, session.Connection, stringWriter);
}
}
public void CanConfirmDatabaseMatchesMappings()
{
var allClassMetadata = NHibernateSession.GetDefaultSessionFactory().GetAllClassMetadata();
foreach (var entry in allClassMetadata)
{
NHibernateSession.Current.CreateCriteria(entry.Value.GetMappedClass(EntityMode.Poco))
.SetMaxResults(0).List();
}
}

In Fluent NHibernate you can use SchemaAction.None to disable the generation of the schema for a particular table.
Example:
public class BankInfoMap : ClassMap<BankInfo>
{
public BankInfoMap()
{
Schema(“viplookups.dbo”);
Table(“bnkroute”);
SchemaAction.None();
Id(x => x.Id).Column(“bnkrouteid”);
Map(x => x.AbaNumber).Column(“crouting”);
Map(x => x.Name).Column(“ccompname”);
Map(x => x.City).Column(“ccity”);
Map(x => x.State).Column(“cstate”);
Map(x => x.PhoneNumber).Column(“cphone1″);
}
}
Taken from: http://lostechies.com/rodpaddock/2010/06/29/using-fluent-nhibernate-with-legacy-databases/

Related

Fluent Nhibernate - make References from children side only

I have Survey that HasMany Question that HasMany Option.
I want to create a reference from Option to Survey.
The problem is that Every time I want to create a new Survey with all of its children, I need to loop on all survey.Questions and loop on all survey.Questions.Options and set o each Option the Survey (or else Nhibernate will create a new Survey for each Option)
** I dont want to have IList<Option> on Survey if I do that it will solve the problem but it would be heavy and unusable **
public class SurveyMap : ClassMap<Survey>
{
public SurveyMap()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x => x.Questions).KeyColumn("SurveyId").Inverse().AsBag().Not.LazyLoad().Cascade.SaveUpdate();
}
}
public class QuestionMap : ClassMap<Question>
{
public QuestionMap()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x => x.Options).KeyColumn("QuestionId").Inverse().AsBag().Cascade.SaveUpdate();
}
}
public class OptionMap : ClassMap<Option>
{
public OptionMap()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.Survey).Column("SurveyId").Cascade.All();
}
}
It might be better to separate your reporting concerns from your applications business logic concerns. Use the mappings that you already have for your applications business logic, and provide a different interface into the data for your reporting requirements. You could use either HQL, SQL or new objects and maps for this.

Can I force nHibernate to persist a new entity with the same ID as an already-loaded entity?

My project has a Ticket entity with an OwnedBy property. I'm using nHibernate to persist the tickets to a database.
The canonical source for potential ticket owners is Active Directory. Since I don't want to have to query Active Directory every time I load tickets, I also persist Ticket.OwnedBy to the database and load it from there when fetching tickets.
When a ticket's owner is reassigned, I get the new Owner from Active Directory and assign it to Ticket.OwnedBy, then call Session.SaveOrUpdate(ticket). When I commit the transaction, NHibernate throws a NonUniqueObjectException because an Owner with the same ID is already associated with the session.
Class definitions
class Ticket {
public int Id { get; set; }
public Owner OwnedBy { get; set; }
/* other properties, etc */
}
class Owner {
public Guid Guid { get; set; }
public string Name { get; set; }
public string Email { get; set; }
/* other properties, etc */
}
Fluent nHibernate Mappings
class TicketMap : ClassMap<Ticket> {
public TicketMap() {
Id(x => x.Id);
References(x => x.OwnedBy)
.Cascade.SaveUpdate()
.Not.Nullable();
/* other properties, etc */
}
}
class OwnerMap : ClassMap<Owner> {
public OwnerMap() {
Id(x => x.Guid)
.GeneratedBy.Assigned()
Map(x => x.Name);
Map(x => x.Email);
/* other properties, etc */
}
}
Sample code
// unitOfWork.Session is an instance of NHibernate.ISession
Ticket ticket = unitOfWork.Session.Get<Ticket>(1);
Owner newOwner = activeDirectoryRepo.FindByGuid(/* guid of new owner, from user */);
ticket.OwnedBy = newOwner;
unitOfWork.Session.SaveOrUpdate(ticket);
unitOfWork.Commit(); // Throws NonUniqueObjectException
I want nHibernate to overwrite the properties of the existing Owner with the properties of the unattached one. (The Name or Email in the object I fetched from AD may be different, and AD is supposed to be the canonical source.) I've tried calling Session.SaveOrUpdateCopy(ticket.OwnedBy) and Session.Merge(ticket.OwnedBy) before the SaveOrUpdate(ticket), but the exception is still being thrown. I've also read this related question about NonUniqueObjectException, but calling Session.Lock() didn't work either.
I have two questions:
Is there an easy way to bend nHibernate to my will?
I may have made an architectural misstep in trying to treat the owners I fetch from AD as the same type as the owners I store in the DB. How can I improve this design so I won't need to bend nHibernate to my will?
Merge works, the most likely issue is you did not call it properly. Merge will update the existing object with the new object properties but Merge does not attach the new object. So you have to use the existing one. If you use the new object after a merge you still get the same error.
The following code should fix the problem:
//Merge the new Owner
unitOfWork.Session.Merge(newOwner);
//Get a valid Owner by retrieving from the session
Owner owner = session.Get<Owner>(newOwner.Id);
// Set the ticket to this owner instance instead of the new one
ticket.OwnedBy = owner;
unitOfWork.Session.Update(ticket);
unitOfWork.Commit();
The Owner retrieved from the session by Get will have the newOwner properties but also be valid for the session.
In order to persist the detached instance of an existing Owner-entity, it should be enough to call merge on the Owner instance without the call to SaveOrUpdate. It will then either insert the entity or update the existing one.
If merge does not work, then something is wrong. Post more code in that case and post your mapping.
BTW: Do you persist the Tickets, too? If so, the mapping seems rather odd. You should have a unique ID on Ticket and Map OwnedBy as a Reference, probably as an inverse mapping with a cascade on it.
Update:
You should map it from both sides. Map the owner-side as a HasMany to Tickets with your Cascade and as Inverse Mapping. Map the Ticket side as Cascade.None() and as a Reference.
public TicketMap() {
Id(x => x.Id);
References(x => x.OwnedBy)
.Cascade.None()
.Not.Nullable();
/* other properties, etc */
}
class OwnerMap : ClassMap<Owner> {
public OwnerMap() {
Id(x => x.Guid)
.GeneratedBy.Assigned()
Map(x => x.Name);
Map(x => x.Email);
HasMany<Ticket>(x => x.Tickets).KeyColumn("TicketId").Cascade.AllDeleteOrphan().LazyLoad().Inverse().NotFound.Ignore();
/* other properties, etc */
}
That should work nicely.

NHibernate explicit fluent column mapping

I have a set of fluent object mappings that looks like this:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Map(x => x.Id);
Map(x => x.Status);
}
}
public class SpecialUserMap : SubClassMap<SpecialUser>
{
public SpecialUserMap()
{
Map(x => x.Property);
}
}
public class DirectoryMap : ClassMap<Directory>
{
public DirectoryMap
{
Map(x => x.Id);
HasMany(x => x.SpecialUsers).Where("Status = 0");
}
}
User is a join table, which SpecialUser joins against to get things like status. However, when I try to reference a SpecialUser in Directory's SpecialUsers collection, I get an error of "Undefined column 'Status'", as in the generated SQL, NHibernate tries to grab the Status column from the SpecialUser table, and not the User table.
Is there a way to explicitly tell NHibernate which table to get the Status column in the DirectoryMapping?
The Status property of a User / SpecialUser needs to map onto a single column in the database. You can't have it coming sometimes from User and sometimes from SpecialUser.
As a workaround, you could add a SpecialUserStatus property to SpecialUser, and then you could query on that easily.
That mappings looks right for table-per-subclass mapping, assuming that SpecialUser extends User. My guess is that it's a bug.

No persister for... {SUBCLASS} NHibernate with Fluent NHibernate

How do I let NHibernate ignore extra properties of a subclass of my model?
class SuperModel { // hot I know
{
public Guid Id { get; private set; }
public string FirstName { get; set; }
}
class SubModel : SuperModel {
public string FavoriteColor { get; set; }
}
I really only want to store the SuperModel data using my repository and use the FavoriteColor elsewhere, but I get
No persister for: SubModel
even though I save it with my repository as
void Store(SuperModel model) {
using (var session = Session){
session.SaveOrUpdate(model); // <<<< The exception is thrown here
}
}
and some where else I use
void WhatToDo(SubModel model) {
doSomething(model.FavoriteColor);
}
And I use it as such
var model = new SubModel { FirstName = "Miranda", FavoriteColor = "Green" };
modelRepository.Store(model);
someService.WhatToDo(model);
Any one know how I can fluently configure this? Thanks.
FYI- implicit and explicit casting has no effect.
Edit
My mappings are like this
class SuperModelMap : ClassMap<SuperModel>
{
public SuperModelMap()
{
WithTable("SuperModels");
Id(x => x.Id);
Map(x => x.FirstName);
}
}
Edit 2
I figure/found out that I could do this, but in my database, I have to have a dummy table, which would just be inefficient. It works but there has to be a better way...
In my SuperModelMap...
JoinedSubClass<SubModel>("SubModel", MapSubModel);
private void MapSubModel(JoinedSubClassPart<SubModel> part)
{
// Leave this empty
}
Edit 3
I'm closer, but I still get a different error on selection.
I tried this.
DiscriminateSubClassesOnColumn("Id")
.SubClass<SubModel>(m => { });
InnerException {"Object with id:
5586b075-47f1-49c8-871c-9c4d013f7220
was not of the specified subclass:
SuperUser (Discriminator was:
'1000')"} System.Exception
{NHibernate.WrongClassException}
You can refine this solution to make it more reusable. As I understand, you don't like mapping duplication. This can be avoided:
I have created a SuperModelMapHelper class that contains an extension method:
public static class SuperModelMapHelper
{
public static void MapSuperModel<T>(this ClassMap<T> classMap)
where T : SuperModel
{
classMap.WithTable("SuperModels");
classMap.Id(x => x.Id);
classMap.Map(x => x.FirstName);
}
}
As you can see - it's generic and will accept any of SuperModel's subclasses. Then, there are two mappings:
public class SuperModelMap : ClassMap<SuperModel>
{
public SuperModelMap()
{
MapSuperModel();
}
}
public class SubModelMap : ClassMap<SubModel>
{
public SubModelMap()
{
MapSuperModel();
}
}
I've used extension method to preserve convention of FluentNHibernate, you can make it simple static method and pass class map as a parameter.
And this code:
Guid id;
using (var session = sf.OpenSession())
using (var transaction = session.BeginTransaction())
{
var subModel = new SubModel()
{FavoriteColor = "blue", FirstName = "Jane"};
session.Save(subModel);
id = subModel.Id;
transaction.Commit();
}
using (var session = sf.OpenSession())
using (var transaction = session.BeginTransaction())
{
var superModel = session.Get<SuperModel>(id);
Console.WriteLine(superModel.GetType().Name);
Console.WriteLine(superModel.FirstName);
transaction.Commit();
}
Works as intended - type is SuperClass. Note that I've created second session. You'd have to flush your session before trying to load entity in the same session you saved it, because NHibernate defers query execution.
Using this solution there is very little duplication. You can investigate AutoMapping feature of FluentNHibernate to reduce it even more - perhaps creating own convention would let you to automatically map such classes.
NHibernate assumes that you'd like to retrieve exactly the same object as you persist. So, even though you don't care about additional properties, you might care about the type of object. If you don't, the simplest solution would be to make a shallow copy of SubModel object, but instead of creating SubModel object, create SuperModel object.
I assume you thought about this and didn't like it. If you'd like to avoid dummy table, but can live with dummy column, I'd suggest you call:
DiscriminateSubClassesOnColumn("dummycolumn")
.SubClass<SubModel>(m => { });
This column would be used by NHibernate to store information about persisted object's type. When you load object from the db, it will be SubModel or SuperModel, depending on what it was when you persisted it.
Your solution with calling DiscriminateSubClassesOnColumn didn't work, because NHibernate couldn't determine which class to use based on id column.
Another idea: I'm not sure if it will work, but you could add another mapping, for SubModel, exactly the same as for SuperModel. Then, NHibernate should persist SubModel to the same table as SuperModel, and when you request your object it should fetch SuperModel object. Unfortunately I can't test this solution right now, maybe you can get it to work. No SubClass in this solution - two "parallel" mappings.

Sql 2008 Filestream with NHibernate

I am attempting to use Filestream in sql server 2008 to store user uploaded images.
My problem is that NHibernate will not error, but it also will not save the data into the database. No record is created.
The Image class below is a custom class (not to be confused with System.Drawing.Image)
public class ImageMap : ClassMap<Image>
{
public ImageMap()
{
WithTable("Images");
Id(x => x.ImageId).GeneratedBy.GuidComb().WithUnsavedValue(Guid.Empty);
Map(x => x.ImageData);
Map(x => x.Extension);
References(x => x.Owner, "UserId");
}
}
My method to save looks like this:
public void Save(Image image)
{
ISession session = GetSession();
session.SaveOrUpdate(image);
}
Maybe I'm saving wrong, maybe my mapping is off. ImageData is a varbinary(max) field in the database.
The answer to this was simple. I deleted my override to the save method and it fixed itself. Our baseclass already had the save method defined and my override was just plain wrong.
public void Save(T entity)
{
using (ISession session = GetSession())
using (ITransaction tx = session.BeginTransaction())
{
session.SaveOrUpdate(entity);
tx.Commit();
}
}