Foreign key is null when NH save the childs collection - nhibernate

I try to map NHibernate to an existing database structure. Unfortunately I can not change the existing structure.
First I'll give some background and then explain the problem itself
Relational analysis is quite simple:
Log is the main Entity. he has one-to-one relationship with Form.
the foreign key is FormID.
Buyer & Seller are collection of the form entity.
The application should save the form entity together with the buyers and the sellers.
The problem is the primary key consisting of the Buyer & Seller entity.
The key composite from ForegineKey- FormID and an intenal ordering int number- tdNum
This is reflected in the following form FNH mapping
public class FormLogMap : ClassMap<FormLog>
{
public FormLogMap()
{
Table("BillOfSaleLog");
Id(x => x.FormId).Column("FormID").GeneratedBy.Native();
....
....
References<Form>(x => x.Form, "FormID").LazyLoad().ReadOnly();
}
}
public class FormMap : ClassMap<Form>
{
public FormMap()
{
Table("BillOfSaleForm");
Id(x => x.Id).Column("FormID").GeneratedBy.Foreign("Log");
...
...
HasOne<FormLog>(x => x.Log).Cascade.All();
HasMany(x => x.Buyers).KeyColumn("FormID").Inverse().Cascade.All();
HasMany(x => x.Sellers).KeyColumn("FormID").Inverse().Cascade.All();
}
}
public class BuyerMap : ClassMap<Buyer>
{
public BuyerMap()
{
Table("BillOfSaleBuyer");
CompositeId()
.KeyReference(x => x.Form, "FormID")
.KeyProperty(x => x.InnerOrderId, "tdNum1");
....
....
}
}
Seller is exectly the same
The problem occurs when I try to save the entity by the Edit Action
I'm using MVC to get the user data and make it an object.
The binding works fine and makes the object along with the collections.
But when I save an entity receive the following error:
I expect NHibernate be smart enough to set the Buyer & Seller foreign keys.
But in fact they remain without value.
The problem can be solved by setting foreign keys manually
as the following code:
//i have to set the form proerty in the childs
//without of the lines NH will try to save the childs it with FormID = null
foreach (var buyer in form.Buyers) { buyer.Form = form; }
foreach (var seller in form.Sellers) { seller.Form = form; }
But I'm looking for an elegant and correct solution
Thank you for reading

You don't show the code for adding a Buyer or Seller to a Form but it should look like this:
form.Buyers.Add(buyer);
buyer.Form = form;
Because Form is the inverse side of the relationships, you have to set the reference to Form on the many side. You should do this anyway so that the in-memory objects are correct. If the Buyer and Seller objects are already persistent in the same ISession than that should work.

According to this related question, HHibernate does not support cascade working with a composite key (as you have in BuyerMap). One of the answers does contain a hack to workaround this, but you will end up with a redundant column.

Related

By code mapping of many-to-many with OrderBy

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.

Pulling a property up to the parent object level

Take the following NHibernate scenario:
I have two tables:
Header
HeaderID (int),
ForeignKey1 (int),
ForeignKey2 (int)
Child
ForeignKey1 (int),
ForeignKey2 (int)
Description (varchar)
...
Metric Shed Ton Of Other Columns That Im Not InterestedIn(various types)
I'm using Fluent NHibernate - I'd like to project the value of Description on the Child object into the parent (is that the correct terminology?!) - but obviously Child contains a lot of data in its columns - I don't want all that extra data, just the description...how do I get NH to produce the equivalent of the following query:
select
Header.*, Child.Description
from
Header
inner join
Child ON Header.ForeignKey1 = Child.ForeignKey1 AND Header.ForeignKey2 = Child.ForeignKey2
The only way I've got this working up to now is to use a Reference mapping to reference the child from the Header entity and then just created a non-mapped property on Header which pointed to Child.Description. Obviously this means that NH fetches the whole child object before it can query the value of Description
(I don't think the composite key is a problem here, the join seems to work fine - it's just how to get the data without getting all the non-interesting data)
At the moment my header entity looks like this:
public virtual int HeaderID { get; set; }
public virtual int KeyColumn1 { get; set; }
public virtual int KeyColumn2 { get; set; }
public virtual Child Child { get; set; }
public virtual string Description { get { return Child.Description; } }
The mappings for it:
Id(x => x.HeaderID);
Map(x => x.KeyColumn1);
Map(x => x.KeyColumn2);
References<Child>(x => x.Child).Fetch.Join().Columns("KeyColumn1", "KeyColumn2").ReadOnly();
Basically, I can't change the schema, I'm not interested in the rest of the data in the Child table, and I can't create views (can't change schema)
Anyone have any ideas if this is possible? I'm going to be querying a big list of Header objects, and I need that field from Child but I don't want the query to take forever!
Is this something I would have to do at the query level instead using hql or crit API?
Edit:
Trying to get this working using query API
Header.Session.QueryOver<Header>(() => parent)
.Where(h => h.HeaderID == 1)
.Inner.JoinQueryOver(x => x.Child, () => child)
.Select(x => x.HeaderID, x => x.ForeignKey1, x => x.ForeignKey2, x => child.Description);
Checking the SQL shows the query is exactly what I want - but I get an exception System.Object[] is not of type Header and cannot be used in this generic collection
I assume this is because what I'm getting is just an array of values from the Select() - any idea how I can transform this into a Header object?
Edit: I ended up with
Header.Session.QueryOver<Header>(() => parent)
.Where(h => h.HeaderID == 1)
.Inner.JoinQueryOver(x => x.Child, () => child)
.Select(x => x.HeaderID, x => x.ForeignKey1, x => x.ForeignKey2, x => child.Description)
.TransformUsing(new GenericResultTransformer(typeof(Header), "HeaderID", "ForeignKey1", "ForeignKey2", "Description"));
Which works just how I want it - if anyone has a better suggestion I'm open to it, but like I said, I can't touch the schema at all
There is the concept of lazy properties, but that is really for the opposite situation, i.e. there is a small number of properties that you want to exclude.
If this is for a presentation scenario, use can use any of NHibernate's query methods, to project just the columns you like - the query result need not be a complete entity. See for instance the select clause in HQL and LINQ, and the SetProjection() family in Criteria/QueryOver.

Insert new NHibernate entity and immediately assign to parent

I have parent and child objects BOOKLET and DEMOGRAPHICS_INFO in my Oracle database mapped as follows in my data layer:
public BookletMapping()
{
Table("BOOKLET");
Id(x => x.Id, m => m.Column("ID");
...
ManyToOne(x => x.DemographicsInfo, m => m.Column("DEMOGRAPHICS_INFO_ID"));
}
public DemographicsInfoMapping()
{
Table("DEMOGRAPHICS_INFO");
Id(x => x.Id, m => m.Column("ID");
...
}
I have intentionally left out the DemographicsInfo relationship to the Booklet because I don't need to traverse my entities that direction. The ManyToOne relationship will actually be a one-to-one.
I have written a test to ensure I can create a DemographicsInfo and immediately assign it to its parent Booklet, and that looks like this:
[Test]
public void ShouldSaveCorrectEntity()
{
var booklet = _unitOfWork.Get<Booklet>(4);
var demInfo = new DemographicsInfo();
_unitOfWork.Insert(demInfo);
booklet.DemographicsInfo = demInfo;
_unitOfWork.Save();
demInfo.Id.ShouldNotEqual(0);
}
When I call Save(), I get the following exception:
{"ORA-02291: integrity constraint (<schema>.BOOKLET_DEMOGRAPHICS_INFO_FK1) violated - parent key not found\n"}
This is because the demInfo object is not given an Id upon Insert(). My Insert implementation looks like this:
public void Insert<T>(T entity)
{
using (var transaction = _session.BeginTransaction())
{
_session.Save(objectToSave);
_session.Flush();
transaction.Commit();
}
}
Where _session is an NHibernate ISession. Because I have both Saved the new entity (it persists successfully) and Flushed my session, I would expect my demInfo variable to have an Id, but it remains 0, which is a foreign key violation when I try to save my parent object. Am I overlooking a step here? Should I rethink my pattern for adding a new child to an existing parent?
I have solved my issue. As it turns out, the Oracle convention of using Sequences for Id column values and populating them using Triggers on row insert disallows NHibernate from learning the Id upon persisting an entity. I added a Sequence Generator to my Id maps and Insert() now updates the Id on my transient entity upon save so I can assign the DemographicsInfo to my Booklet.DemographicsInfo.
Id(x => x.Id, m =>
{
m.Column("ID");
m.Generator(Generators.Sequence, a => a.Params(new { sequence = "SEQ_TABLE_ID" }));
});
Now my test passes with no exceptions.

Simple Delete in Entity Framework using WCF (many to many relationship)

I have a simple database model containing 3 Tables : Companies, Categories and CompanyCategories (which is a relation table with only 2 FK : CompanyID and CategoryID).
My edmx model it only shows Companies and Categories tables (CompanyCategories is somehow hidded since its a simple many to many relationship table).
In the WCF service, I have a GetDatabase() function that returns all the database objets wrapped in one big custom object :
[OperationContract]
public FullDatabase GetDatabase()
{
DBEntities context = new DBEntities ();
FullDatabase mydb = new FullDatabase();
mydb.Companies = context.Companies.ToList();
mydb.Categories = context.Categories.ToList();
return mydb;
}
[OperationContract]
public FullDatabase UpdateDatabase(FullDatabase db)
{
// Here is my problem when removing a category from a company on
// the client its been brought back in my db object
}
class FullDatabase()
{
List<Company> Companies;
List<Category> Categories;
}
On the client now, I use GetDatabaseAsync() to retrieve the database in a _FullDB variable. Now using that variable I tried the following :
// Adding a category like that Works well
Company c = _FullDB.Companies.First();
c.Categories.Add(_FullDB.Categories.First());
wcfServiceClientObject.UpdateDatabaseASync(_FullDB);
.....
// Removing a category, doesn't work though :
Company c = _FullDB.Companies.First();
c.Categories.Remove(_FullDB.Categories.First());
wcfServiceClientObject.UpdateDatabaseASync(_FullDB);
// here my c.Categories.Count is updated correctly to delete the item
// but when on the server after (in the UpdateDatabase function) the item
// I deleted is still there
I really dont understand why the Add would work but not the Remove.
Finally found the problem. Now it works, but I'm not sure it is the best way to do it.
When removing the category from the company, I also had to also remove the company from the category...
Company comp = _FullDB.Companies.First();
Category cat = _FullDB.Categories.First();
comp.Categories.Remove(cat);
cat.Companies.Remove(comp);
wcfServiceClientObject.UpdateDatabaseASync(_FullDB);

Refreshing collections filtered in the mapping

I have a collection which is filtered at the mapping level to enable soft deletion using an "isDeleted" column in the database.
The mapping looks like this:
HasMany(x => x.UploadedFiles).Where("IsDeleted = 0")
When I set the isDeleted property for some items the collection does not automatically refresh to reflect the deletion until I reload the entity.
Is there any way to force a "refiltering" without reloading the entity ?
The Where clause in the mapping is to filter during fetching. It is not used at run-time, which is why you're not seeing UploadedFiles drop out of your collection when you set IsDeleted = true. I don't believe it is possible to refresh the collection without reloading the entity that owns it.
I would recommend expressing your intent in your object model.
private IList<File> uploadedFiles = new List<File();
public virtual IEnumerable<File> UploadedFiles {
get {
return uploadedFiles.Where(x => x.IsDeleted == false);
}
}
And then modifying your mapping to access your backing field...
HasMany(x => x.UploadedFiles)
.Access.CamelCaseField()
.Where("IsDeleted = 0")