Building Fluent NHibernate Maps at runtime - fluent-nhibernate

I am working on a project that looks at the database created by Mirth Connect v3.
In this version, tables are created at run time based on channels created.
For example, if I create a new channel, it creates an entry in a table that has a unique id. Then, using that id, a set of tables is created with that id added onto the end of the name. ie for a channel with id 8, a table called d_ms8 (among others) is created.
All of these tables (d_ms*) have the same structure.
What I am wondering is whether or not there is a way to map this sort of thing in Fluent NHibernate.
public class MapChannelStats : ClassMap<ChannelStatsObj>
{
public const String TableName = "d_ms8";
public MapChannelStats()
{
Table(TableName);
Id(x => x.MetadataID).Column("metadata_id");
Map(x => x.ServerID).Column("server_id");
Map(x => x.Errored).Column("error");
Map(x => x.ErroredLifetime).Column("error_lifetime");
Map(x => x.Filtered).Column("filtered");
Map(x => x.FilteredLifetime).Column("filtered_lifetime");
Map(x => x.Received).Column("received");
Map(x => x.ReceivedLifetime).Column("received_lifetime");
Map(x => x.Sent).Column("sent");
Map(x => x.SentLifetime).Column("sent_lifetime");
}
}
Thanks,
Bruce.

It is a difficult thing what you are asking, because I think you are not able to change the table's name at runtime. However, if you change your approach, there should be some possibilities, like:
You could create a column to specify the tenant ID, instead of creating a whole table for it. Then, you can set a filter specifically for this column (reference), which you can change like this:
Session.EnableFilter("tenant-filter").SetParameter("TenantId", "2");
Or you could create a database for which tenant and use a component like Boot.Multitenancy.

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

When does NHibernate JoinQueryOver load full collection?

I've got a model where a Person has a lazy-loaded collection of Registrations. My NHibernate 3.3.3 query uses JoinQueryOver across that relation and some more:
var reg = Session.QueryOver<Person>()
.Where(p => p.ID == pid)
.JoinQueryOver<Registration>(p => p.Registrations)
.Where(r => r.IsDropped == false)
.JoinQueryOver<Slot>(r => r.Slot)
.JoinQueryOver<Position>(s => s.Position)
.JoinQueryOver<AcademicTerm>(p => p.Term)
.Where(t => t.ID == currentTerm.ID)
.SingleOrDefault();
In the log, I see the expected SQL for the query, and I get the expected Person instance, but when I access the Registrations collection, the full collection of registrations gets (lazy) loaded. I was expecting it would just be a partial collection of the registrations that meet the criteria.
In a previous question, I had the opposite problem, and fixed it by adding a Fetch() clause to the query.
When does a JoinQueryOver criterion cause the root entity to have a partial collection, and when does it have an unloaded collection that will get lazy loaded? I feel like I should understand this.
My fluent mapping for Person:
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(x => x.ID).GeneratedBy.Guid();
Map(x => x.Username).Length(20).Unique();
Map(x => x.HashedPassword).Length(Security.HashedPasswordEncodedLength);
Map(x => x.PasswordSalt).Length(Security.PasswordSaltEncodedLength);
Map(x => x.LastName).Length(25).Index("FullName");
Map(x => x.FirstName).Length(20).Index("FullName");
Map(x => x.StudentID).Length(12).Nullable();
Map(x => x.EmployeeID).Length(12).Nullable();
Map(x => x.EmailAddress).Length(72);
Map(x => x.IsAdmin).Nullable();
Map(x => x.IsAgencyStaff).Nullable();
Map(x => x.IsCenterStaff).Nullable();
Map(x => x.IsFaculty).Nullable();
Map(x => x.IsRegistrant).Nullable();
Map(x => x.IsStudent).Nullable();
Map(x => x.Type);
Map(x => x.AuthenticationMode);
HasMany<Registration>(x => x.Registrations).KeyColumn("Registrant_id")
.LazyLoad()
.AsBag()
.Inverse()
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate();
}
}
Update
Comparing this with my other question (linked above), where I had the opposite problem, that one uses a Left.JoinQueryOver to get from the base entity to the collection. I tried that here, and then got the expected partial collection. (I don't actually need the left join in the other case; I don't remember why it's there.)
The records fetched are the same with or without the left join, but NHibernate populates the collection in one case, and not in the other. I doubt that's behavior I can depend on, so I've changed my strategy to query for the registrations separately.
This condition in the QueryOver:
...
.JoinQueryOver<Registration>(p => p.Registrations)
.Where(r => r.IsDropped == false)
...
Is in fact a WHERE clause for a Person. I mean, this condition is deciding if there is such a Person, having at least one Registration which is NOT dropped. It is not a way, how to filter the Registration colleciton. BUT:
NHibernate has two nice ways how to filter collections themselves. These are where clause and filter.
1. where clause
Easily said: we can extend the collection mapping, whit a SQL Statement, which will be always added as a where condition. See where 6.2. Mapping a Collection, extract:
where="arbitrary sql where condition"
== (optional) specify an arbitrary SQL WHERE condition to be used when retrieving or removing the collection (useful if the collection should
contain only a subset of the available data)
so in our case the mapping:
HasMany<Registration>(
...
.Where("IsDropped = 0"); // SQL Statement
2. filter setting
While where clause is static, baked into the assembly/mapping... filter is a way how to do that dynamically. So just imagine, that we would sometimes like to have a set of Registrations, which are dropped, next time, which are not dropped.
We would like to be able to change "IsDropped = 0" to "IsDropped = 1" (better scenario could be switch by culture or lang). And that's where the filter could be applied, see 18.1. NHibernate filters
There is a small draft of the filter stuff with fluent, see the links below to get a complete picture.
HasMany
...
.ApplyFilter<MyFilter>()
And filter could be like:
public class MyFilter: FilterDefinition
{
public MyFilter()
{
WithName("dropped")
.WithCondition("IsDropped == :isDropped")
.AddParameter("isDropped", NHibernate.NHibernateUtil.Boolean);
}
}
And finally, we can enable the Filter for a whole session (coool, all collections will be filtered the same way in that transaction/session, all collection mapped with MyFilter)
session.EnableFilter("dropped").SetParameter("isDropped", false);
See more here:
Syntax to define a NHibernate Filter with Fluent Nhibernate (NOT Accepted answer)
How do you do additional filtering on a HasMany mapping in Fluent nHibernate?

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.

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 dont update child

i have a Class Map CardMatch which have Employee and Card as reference
Id(x => x.MatchId).GeneratedBy.Sequence("CARDMATCH_SEQ").Column("MATCHID");
References(x => x.Employee).Column("EMPNO");
References(x => x.Card).Column("CARDID").LazyLoad(Laziness.False).Cascade.SaveUpdate();
CardMatch _cm = _rep.getById(1);
_cm.Card.CardLimit = 500;
_rep.Update(_cm);
doesnt update the Child (Card). How can i update the Child?
IMO your FNH mapping is correct. Flush method should be called on ISession instance. The last line is most likely redundand because NH tracks changes on its own.
CardMatch _cm = _rep.getById(1);
_cm.Card.CardLimit = 500;
_rep.Flush(); // save changes