Nhibernate ByCode Mapping -- How to avoid a null Many-To-One Object Map? - nhibernate

I'm mapping a legacy database and previously users were not required to select a country when they signed up and later added it.
So there are some users who have a CountryID that is NULL. So when my many to one mapping (ByCode) tries to load up the corresponding country details, it finds none, so the user's "country" object is null.
When I try to display user.Country.Name for example, I get a object reference not found.
I could hack this by making a country with an id of -1 called Undisclosed and then change all the null country ids to -1, but that's cheating to solve a common problem I would think.
So my question is how can I map so that if the CountryID is not found, that user gets a new Country object.
My mapping in the user object is:
ManyToOne<Country>(x => x.Country, m =>
{
m.Column("CountryID");
m.Lazy(LazyRelation.NoLazy);
m.NotNullable(false);
m.Fetch(FetchKind.Join);
});
My CountryMap is pretty basic...
public CountryMap()
{
Id(p => p.ID, m => m.Generator(Generators.Identity));
Property(p => p.Code);
Property(p => p.Name);
}

Coding too late... brain freeze...
I simply modified the Country property on the User POCO to return a new Country() if the country was NULL.
Hope this helps someone, but if admins delete the question I won't be offended. :) :) :)

Related

Can I transform the value of my primary key in a specific one-to-many relationship using Fluent API?

I'm trying to manually scaffold part of an existing database schema in EF Core 2.2. The database in question is part of a 3rd party ERP software, and I have zero control over the design of the database itself, so I'm trying to work with what I am given. So far it's going okay, however I am hitting a snag due to a questionable database design choice by the ERP vendor.
Consider the following one-to-many relationship I'm trying to build in my OnModelCreating() method within my DbContext (via Fluent API):
modelBuilder.Entity<SalesOrderHeader>(s => {
s.HasMany<WorkOrderHeader>(e => e.WorkOrders)
.WithOne()
.HasPrincipalKey(e => e.SalesOrderNumber)
.HasForeignKey(e => e.RelatedPO_SONumber);
});
This is almost what I need, however, in the WorkOrders table "RelatedPO_SONumber" is of type string, where "SalesOrderNumber" in SalesOrderHeaders table is of type int. Further, what's stored in "RelatedPO_SONumber" is a 0-padded string (always 8 characters long) of "SalesOrderNumber" (eg SalesOrderHeader.SalesOrderNumber = 1234567, WorkOrder.RelatedPO_SONumber = "01234567").
Here's what I tried and might help illustrate what I'm trying to do via format specifier (throws ArgumentException "The expression should represent a simple property access: 't => t.MyProperty'"):
modelBuilder.Entity<SalesOrderHeader>(s => {
s.HasMany<WorkOrderHeader>(e => e.WorkOrders)
.WithOne()
.HasPrincipalKey(e => e.SalesOrderNumber.ToString("D8"))
.HasForeignKey(e => e.RelatedPO_SONumber);
});
I tried an alternative approach which was to make another property in my SalesOrderHeader entity definition, and using that as the argument for HasPrincipalKey():
// within SalesOrderHeader
[NotMapped]
public string ZeroPaddedSONumber => SalesOrderNumber.ToString("D8");
// within DbContext OnModelCreating()
modelBuilder.Entity<SalesOrderHeader>(s => {
s.HasMany<WorkOrderHeader>(e => e.WorkOrders)
.WithOne()
.HasPrincipalKey(e => e.ZeroPaddedSONumber)
.HasForeignKey(e => e.RelatedPO_SONumber);
});
This resulted in:
"InvalidOperationException: No backing field could be found for property 'ZeroPaddedSONumber' of entity type 'SalesOrderHeader' and the property does not have a setter."
Is there a way to pass anything other than a strict property reference, but rather a transformed "version" of that property?"
Thanks in advance.
EDIT: Following a suggestion in a comment, I tried this:
modelBuilder.Entity<SalesOrderHeader>(s => {
s.Property(e => e.SalesOrderNumber)
.HasConversion(n => n.ToString("D8"), s => int.Parse(s));
s.HasMany<WorkOrderHeader>(e => e.WorkOrders)
.WithOne()
.HasPrincipalKey(e => e.SalesOrderNumber)
.HasForeignKey(e => e.RelatedPO_SONumber);
});
This doesn't seem to work either, I get the following exception (I get the same exception if I omit the conversion):
The types of the properties specified for the foreign key {'RelatedPO_SONumber'} on entity type 'WorkOrderHeader' do not match the types of the properties in the principal key {'SalesOrderNumber'} on entity type 'SalesOrderHeader'.

NHibernate Fluent Mapping results in Invalid index n for this SqlParameterCollection with Count=n

I have a class TrainingSpecialty which is mapped as follows:
public TrainingSpecialityMap()
{
Table("TrainingSpecialty");
Id(o => o.Id).GeneratedBy.Native();
DiscriminateSubClassesOnColumn(CandidateTrainingDatabase.TrainingSpecialtyTable.IsCombinedColumn)
.Formula(
string.Format("case when {0} is null then 0 else {0} end", CandidateTrainingDatabase.TrainingSpecialtyTable.IsCombinedColumn));
Map(o => o.Code);
Map(o => o.Name).Not.Nullable();
Map(o => o.TrainingProgramType, "ProgramType").Not.Nullable();
HasMany(o => o.Forms)
.Not.KeyNullable()
.KeyColumn("TrainingSpecialtyID").Cascade
.SaveUpdate();
Map(o => o.ProcedureTitle).Nullable();
HasMany(o => o.Procedures);
}
I'm attempting to create a new Form, add it to the TrainingSpecialty.Forms list and then update the TrainingSpecialty. The purpose of this is to add the new form. (Note that Form does NOT have a navigation property back to the TrainingSpecialty. Without changing the Form class, there is no simple way to just add the form with a reference to the TrainingSpecialty, which is why I'm updating the TrainingSpecialty.)
The first error I'm getting upon Updating the TrainingSpecialty is Error dehydrating property value for Domain.EvaluationForm.Domain.TrainingSpecialty.FormsBackref.
The inner exception is Invalid index 3 for this SqlParameterCollection with Count=3.
There should always be one previously existing form and I'm now attempting to add a second. I've looked for fields that are duplicately mapped (as per others who have had this issue).
How is this mapping off?
It was a duplicate mapping, just like so many others like this. In this case the Form mapping someone had added a reference back to the Training Specialty.

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.

Foreign key is null when NH save the childs collection

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.