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

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'.

Related

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.

Kohana 3.3 ORM - how to find all ancestors in a tree (self-referencing table)

How to find all the ancestors (not only direct parent) of a record with the following model:
class Model_Category extends ORM {
protected $_belongs_to = array(
'parent' => array('model' => 'Category', 'foreign_key' => 'category_id'),
);
protected $_has_many = array(
'children' => array('model' => 'Category', 'foreign_key' => 'category_id'),
);
$category->parent->find() is only giving the direct parent and even when trying to fetch the parent of the parent with a recursive function it throws Kohana_Exception: Method find() cannot be called on loaded objects.
This occurs so natural to me that I guess there must an easy way to do it - I'm not sure if it's me or the lack of documentation on ORM relations - halp!
You should be able to do this with a recursive function or a while loop
$current = $category;
while ($current->parent->loaded())
{
//save $current
$current = $category->parent;
}
Use Nested Sets. For example, https://github.com/evopix/orm-mptt. It has special methods like parents(), children(), siblings() etc. Of course, this requires modifications in your DB table.
Ok, turns out the related models actually ARE included, cause if you do
$category->parent->parent->name it will be the name of the grand-parent of $category.
My problem was that I was printing $category->parent->as_array() and expecting to see the parent relations in the (multidimensional) array which simply appears not to be the case with kohana.

How do I reference a row using multiple properties in the current entity?

I need to reference a row in a table not by its primary key but by two other columns. How do I configure that in fluent-nhibernate?
I am using FluentNHibernate 1.3
Example: If the other table YY has columns foo and bar (assume datatype is integer if that helps) which are not primary keys. My current class XX has those properties and I want to reference a YY object from XX. Should I put the following in the ClassMap<XX> class if the local member is YYObject and the fields to reference it are XX.foo and XX.bar ?
CompositeId(x => x.YYObject).KeyProperty(x => x.foo).KeyProperty(x => x.bar);
I am also not quite sure how the ClassMap<YY> class should be adjusted accordingly. If you have done such things we could use some guidance.
We are dealing with a legacy situation so I cannot wave my hands and make it all go away.
if possible define a Component around the two properties
Component(x => x.Comp, c =>
{
c.Map(x => x.Foo);
c.Map(x => x.Bar);
});
and reference it
References(x => x.ParentObject)
.KeyColumns.Add("parent_foo", "parent_bar");
.PropertyRef(p => p.Comp);

NHibernate mapping error in legacy mapping

I've inherited a large set of NHibernate mappings that live in an existing, functional application. I've branched this application to develop some new features, and while I do so I'm also extending the testing infrastructure to allow for a more TDD-like approach. But now I've hit a wall in one of my integration tests...
I have a class with test data, which I insert prior to the integration test. In the method that inserts these, I get the following exception:
NHibernate.PropertyAccessException: Invalid Cast (check your mapping for property type mismatches); setter of Domain.Entities.Project ---> System.InvalidCastException: Unable to cast object of type 'System.Object' to type 'Domain.Entities.ProjectModules'.
and I can't figure out why. I have two Project instances that I try to persist in the database on setup, both defined like this:
new Project("2023", "projeName", "projaddr")
{
PrincipalOwner = UserOne, // UserOne and Office are other properties
Office = Office,
// I've tried just not instantiating this too - gave the same exception
ProjectModules = new ProjectModules
{
HasModuleOne = false,
HasModuleTwo = false
});
});
The (relevant part of the) Fluent NHibernate mapping looks like this:
Component(m => m.ProjectModules, c =>
{
c.LazyLoad();
c.Map(x => x.HasModuleOne)
.Column("ModuleOne").Not.Nullable().Default("0");
c.Map(x => x.HasModuleTwo)
.Column("ModuleTwo").Not.Nullable().Default("0");
});
I've solved this - for some reason, NHibernate didn't like when the component mapping was specified inline in the mapping for Projects, but if I moved the mapping to a separate class ComponentMap<T> it worked. So I changed the problematic lines to
Component(p => p.ProjectModules);
and added the following class to my mappings assembly:
public class ProjectModulesMap : ComponentMap<ProjectModules>
{
LazyLoad.Always();
Map(pm => pm.ModuleOne);
Map(pm => pm.ModuleTwo);
}
Then everything worked as I would have expected it to from the start.