How to map NHibernate variable table reference? - c#

Having a table called ChildTable with 2 columns SourceTable and SourceId and some other tables ParentTable1, ParentTable2, etc.
The Id found in SourceId can be used to join to a parent table when the SourceTable has a value associated with that table (1 -> ParentTable1, 2 -> ParentTable2). For example, to get all the ChildTable rows which are associated to rows in ParentTable1 it would be achieved with this query:
select *
from ChildTable ct
join ParentTable1 pt1
on ct.SourceTable = 1 and ct.SourceId = pt1.Id
I would like to map those 2 ChildTable columns as 1 property per parent table: Parent1, Parent2,... so 1 of them would be not null and the rest of the parent properties would be null:
public class ChildClass
{
public Parent1Class Parent1 { get; set; }
public Parent2Class Parent2 { get; set; }
public Parent3Class Parent3 { get; set; }
.
.
.
}
Question is: how to write the mapping for this case (mapping by code if possible)?
Note: This is for mapping existing tables, refactoring the table schema is not a solution yet (but suggestions are welcome).
Update
For the purpose of querying it seems to be enough to map a ChildClass property Parent1 with:
ManyToOne(property => property.Parent1, map => map.Formula("(select pt1.Id from dbo.ParentTable1 pt1 where SourceTable = 1 and pt1.Id = SourceId)"));
and the Children collection of Parent1Class with:
mapper.Where("SourceTable = 1");
For update/insert it is probably achievable using accessors, will post an update later.

Why don't you use Any?
Class:
public class ChildClass
{
public virtual ParentBase Parent { get; set; }
// beware of proxies when casting... this may not work like this
public Parent1Class Parent1 { get { return Parent as Parent1Class; } }
public Parent2Class Parent2 { get { return Parent as Parent2Class; } }
.
.
.
}
Mapping:
Any(x => x.Parent, typeof(int), m =>
{
m.IdType<int>();
m.MetaType<int>();
m.MetaValue(1, typeof(Parent1));
m.MetaValue(2, typeof(Parent2));
m.Columns(
id => id.Name("SourceId"),
classRef => classRef.Name("SourceTable"));
});
There is also many-to-any, which maps collections of any types into a relation table.
When using it in a query, you can check the .class, or use a subquery:
HQL:
select *
from ChildTable ct join Parent
where pt1.class = Parent1
or
select *
from ChildTable ct
Where ct.Parent in (from Parant2 p where p.Property = 'Hugo')

Related

Entity Framework Core: load full hierarchy

Having a self referencing table, with a ParentId attribute which holds the id of the parent record, what can I do so that using ef I will load into each parent its children.
What I want is to transform this cte which will return the full hierarchy as a collection.
var queryString = #"
;WITH cte AS (
SELECT * FROM [dbo].[Folders] _f WHERE _f.[Id] = #id
UNION ALL
SELECT _c.* FROM [dbo].[Folders] _c
INNER JOIN cte _cte
ON _cte.[Id] = _c.[ParentFolderId]
)
SELECT * FROM cte";
return await this.Entities.FromSql(new RawSqlString(queryString), new SqlParameter("id", id)).ToListAsync();
into something that will somehow load the hierarchy of children into their parents, keeping at the same time the performance of one trip to db.
class Folder
{
public int Id { get; set; }
public int? FolderId { get; set; }
public Folder Folder { get; set; }
public IEnumerable<Folder> Children { get; set; }
}
Hierarchy example
- Main (Id: 1 / ParentId: null)
- C1 (2/1)
- C11 (4/2)
- C111 (7/4)
- C12 (5/2)
- C2 (3/1)
- C21 (6/3)
- C211 (8/6)
Configured relation
builder.Ignore(prop => prop.Folder);
builder.HasOne(prop => prop.Folder).WithMany(prop => prop.Children).HasForeignKey(fk => fk.FolderId);
If you want the entire hierarchy in one query, that's easy. Just retrieve all the Folders and if Change Tracking is enabled EF will fix-up all the relationships. IE if you just run
var folders = db.Set<Folder>().ToList();
You'll have the whole hierarcy with all the Navigation Properties populated.
You can get the whole hierarchy with this query:
var hierarchy = db.Set<Folder>().Include(f => f.Children).ToList();

use Linq to form a relationship where none exists in the database

I've been using the .Net Core Linq expressions .Include and .ThenInclude to great success with my Entity Framework Core project.
However, I need to combine 2 models that are not in any type of relationship in the database.
My SQL query looks like this:
SELECT * FROM selectedEnzymes se
LEFT JOIN enzymeDefinitions ed ON se.selectedEnzymeID = ed.selectedEnzymeID
WHERE se.ecosystemID = 7
selectedEnzymes and enzymeDefinitions are both models.
But there is no relationship between them even though they both contained a selectedEnzymeID. In the database, there is no key.
So I was wondering, is there a way to use Linq to combine two models in such a way if no relationship exists?
Thanks!
You can use the LINQ Join and Select as you do in SQL.
Starting with something like this as models and list of both Classes:
public class SelectedEnzymes
{
public int SelectedEnzymeId { get; set; }
public int EcosystemId { get; set; }
public string OtherPropertySelectedEnzymes { get; set; }
}
public class EnzymeDefinitions
{
public int SelectedEnzymeId { get; set; }
public string OtherPropertyEnzymeDefinitions { get; set; }
}
List<SelectedEnzymes> selectedEnzymesList = new List<SelectedEnzymes>();
List<EnzymeDefinitions> enzymeDefinitionList = new List<EnzymeDefinitions>();
You are able to do something like this:
var query = selectedEnzymesList // table in the "FROM"
.Join(enzymeDefinitionList, // the inner join table
selected => selected.SelectedEnzymeId, // set the First Table Join parameter key
definition => definition.SelectedEnzymeId, // set the Secont Table Join parameter key
(selected, definition) => new { SelectedEnzyme = selected, EnzymeDefinition = definition }) // selection -> here you can create any kind of dynamic object or map it to a different model
.Where(selectAndDef => selectAndDef.SelectedEnzyme.EcosystemId == 7); // where statement
So I was wondering, is there a way to use Linq to combine two models
in such a way if no relationship exists?
In fact, this is similar to the method of obtaining two related tables.
You can directly use the following linq to achieve:
var data = (from se in _context.SelectedEnzymes
join ed in _context.EnzymeDefinitions
on se.SelectedEnzymeId equals ed.SelectedEnzymeId
where se.EcosystemId == 7
select new { se.Name,se.LocationId, ed.Name,ed.CountryId }).ToList();
Here is the result:

Linq EF Split Parent into multiple Parents

Using Entity Framework to query a database with a Parent table and Child table with a 1-n relationship:
public class Parent {
public int id { get; set; }
public IList<Child> Children { get; set; }
}
public class Child {
public int id { get; set; }
}
Using EF, here's a quick sample query:
var parents = context.Parents;
Which returns:
parent id = 1, children = { (id = 1), (id = 2), (id = 3) }
What we need is for this to flatten into a 1-1 relationship, but as a list of parents with a single child each:
parent id = 1, children = { (id = 1) }
parent id = 1, children = { (id = 2) }
parent id = 1, children = { (id = 3) }
We're using an OData service layer which hits EF. So performance is an issue -- don't want it to perform a ToList() or iterate the entire result for example.
We've tried several different things, and the closest we can get is creating an anonymous type like such:
var results = from p in context.Parents
from c in p.Children
select new { Parent = p, Child = c }
But this isn't really what we're looking for. It creates an anonymous type of parent and child, not parent with child. So we can't return an IEnumerable<Parent> any longer, but rather an IEnumerable<anonymous>. The anonymous type isn't working with our OData service layer.
Also tried with SelectMany and got 3 results, but all of Children which again isn't quite what we need:
context.Parents.SelectMany(p => p.Children)
Is what we're trying to do possible? With the sample data provided, we'd want 3 rows returned -- representing a List each with a single Child. When normally it returns 1 Parent with 3 Children, we want the Parent returned 3 times with a single child each.
Your requirements don't make any sense, the idea behind how EF and LINQ work is not those repetitive info like SQL does. But you know them better and we don't know the whole picture, so I will try to answer your question hoping I understood it correctly.
If like you said, your problem is that IEnumerable<anonymous> doesn't work with your OData service layer, then create a class for the relationship:
public class ParentChild {
public Parent Parent { get; set; }
public Child Child { get; set; }
}
And then you can use in in your LINQ query:
var results = from p in context.Parents
from c in p.Children
select new ParentChild { Parent = p, Child = c }

NHibernate join does not fully populate objects within transactions

We have a situation where a transaction is started on an NHibernate session, some rows are populated into a couple of tables, and a query is executed which performs a join on the two tables.
Models:
public class A
{
public virtual string ID { get; set; } // Primary key
public IList<B> Bs { get; set; }
}
public class B
{
public virtual string ID { get; set; } // Foreign key
}
NHibernate maps:
public class AMap: ClassMap<A>
{
public AMap()
{
Table("dbo.A");
Id(x => x.ID).Not.Nullable();
HasMany(u => u.Bs).KeyColumn("ID");
}
}
public class BMap: ClassMap<B>
{
public BMap()
{
Table("dbo.B");
Map(x => x.ID, "ID").Not.Nullable();
}
}
A transaction is started and the following code is executed:
var a1 = new A
{
ID = "One"
};
session.Save(a1);
var a2 = new A
{
ID = "Two"
};
session.Save(a2);
session.Flush();
var b1 = new B
{
ID = a1.ID
};
session.Save(b1);
var b2 = new B
{
ID = a2.ID
};
session.Save(b2);
session.Flush();
A a = null;
B b = null;
var result = _session.QueryOver(() => a)
.JoinQueryOver(() => a.Bs, () => b,JoinType.LeftOuterJoin)
.List();
The result is a list of A. In the list, objects of A do not have Bs populated.
Although this example is simplified, the actual objects in question have additional properties associated with corresponding table columns; all those properties populate as expected; the issue is confined to the property mapped as HasMany (foreign key association).
If the table is populated first, and then the query is performed (either as separate processes or in consecutive transactions), the objects of A do have their Bs correctly populated. In other words, it seems as though queries executed in a transaction are not able to see the complete effect of inserts previously performed within the same transaction.
Inspection of the SQL generated by NHibernate confirms that it correctly performed all the inserts and correctly formulated the join query; it appears that it simply did not correctly populate the objects from the query result.
Are there any special steps required to ensure that database inserts/updates performed via NHibernate are fully visible to subsequent fetches in the same transaction?
HasMany(u => u.Bs).KeyColumn("ID");
looks wrong to me. The id of a one-to-many relation should be A_ID.
You do lots of strange things in your code. I hope your real code doesn't look like this. You should not set foreign keys directly. They are managed by NH. You should not Flush all the time. Normally you never flush.
Also note that the left outer join is not used to populate the list of Bs in A. (There is no information for NHibernate that this would be a valid option.) There are mapping tricks to load entities and one of its collections in one query, but this is most of the time not such a good idea and I suggest to not try this unless you really know NH and how queries are processed very well. You'll only get the same A multiple times and some performance problems if you do not break it completely. If you are afraid of the N+1 problem (I hope you are), use batch-size instead.
Figured out the solution. The gist of it is to add the "child" items to the "parent" and then save that.
So... classes now look like:
public class A
{
public virtual string ID { get; set; } // Primary key
public virtual IList<B> Bs { get; set; }
}
public class B
{
public virtual A A { get; set; } // Foreign key now expressed as reference to "parent" object instead of property containing key value
}
ClassMaps for both parent and child express the relationship as object/list:
public class AMap: ClassMap<A>
{
public AMap()
{
Table("dbo.A");
Id(x => x.ID).Not.Nullable();
HasMany(u => u.Bs).KeyColumn("ID").Cascade.SaveUpdate();
}
}
public class BMap: ClassMap<B>
{
public BMap()
{
Table("dbo.B");
Map(x => x.ID, "ID").Not.Nullable();
References(x => x.A, "ID").Not.Nullable();
}
}
Finally, data is saved by constructing the objects and their relationship before saving them i.e. relationships are saved with the objects:
var a1 = new A
{
ID = "One"
};
var b1 = new B
{
A = a1
};
a1.Bs = new []{b1};
session.Save(a1);
var a2 = new A
{
ID = "Two"
};
var b2 = new B
{
A = a2
};
a2.Bs = new []{b2};
session.Save(a2);
session.Flush();
This query:
A a = null;
B b = null;
var result = _session.QueryOver(() => a)
.JoinQueryOver(() => a.Bs, () => b,JoinType.LeftOuterJoin)
.List();
Now returns the expected result, and within the same session/transaction.

Join table in mapping with inverse FK

Assume I have two tables:
Table MY_ENTITY
ID: PK
OTHER_ID: FK to table OTHER
Table OTHER
ID: PK
COL: The column I want
My entity looks like this:
class MyEntity : Entity
{
public virtual Column { get; set; }
}
My auto-mapping override looks like this:
mapping.IgnoreProperty(x => x.Column);
mapping.Join("OTHER", x => x.KeyColumn("ID").Optional()
.Map(y => y.Column, "COL");
This works fine and executes without problems, but the join is wrong.
It creates an SQL statement that joins the PK of MY_ENTITY to the column specified in KeyColumn in the table OTHER. Something along the lines of:
select ... from MY_ENTITY e left outer join OTHER o on e.ID = o.ID
However, I need the join to be like this:
select ... from MY_ENTITY e left outer join OTHER o on e.OTHER_ID = o.ID
How to achieve this?
You'll have to add an OtherId property to MyEntity (it doesn't have to be public; it's just for mapping) and use PropertyRef in the Join Key mapping (that's the method name in mapping by code; it's property-ref in XML, you'll have to look it up for Fluent)
Alternatively, map Other as an entity and use a Reference in MyEntity. You can cascade all, so it get's persisted/deleted together with MyEntity.
Then, just project the referenced property (which will not be mapped in MyEntity):
class MyEntity
{
public virtual PropertyType Property
{
get
{
EnsureOther();
return Other.Property;
}
set
{
EnsureOther();
other.Property = value;
}
}
void EnsureOther()
{
if (Other == null)
Other = new Other();
}
public virtual Other { get; set; }
}
class Other
{
public virtual PropertyType Property { get; set; }
}
Maybe you should use a References (many-to-one) mapping instead.
References(x => x.Other, "OTHER_ID")
.Fetch.Join()

Categories