NHibernate Mapping Property Beyond LINQ Querying - c#

my application has the following entity:
public class User
{
public virtual int UserID { get; set; }
public virtual Membership LatestMembership { get { return Membership.First(); } }
public virtual IList<Membership> Membership { get; set; }
public User()
{
Membership = new List<Membership>();
}
}
With the following mapping:
public UserMap()
{
Table("Users");
Id(x => x.UserID);
HasMany(x => x.Membership)
.KeyColumn("UserID")
.OrderBy("DateAdded DESC")
.Inverse()
.Cascade.All();
}
The LatestMembership property against the user simply grabs the first record from the Membership collection (which is ordered so that the newer records are at the top).
So far so good, however now say i want to do the following (i know this will return them all but i'm just using this as an example):
var users = session.Linq<User>()
.Where(u => u.LatestMembership.DateAdded < DateTime.UtcNow);
An error is thrown because the LatestMembership property is beyond the nhibernate linq providers capabilities. The only solution i have so far is to convert it to a list and then apply the where condition but i'd imagine this could become pretty insuficient for a large database.
I was wondering if there was an alternative way i could map this or what your recommendations are. Thanks

Unfortunately, this isn't really possible using the current Linq provider. One thing you can do is to map the properties of the LatestMembership you want to query on as read-only and use formulas in your mappings to derive the values for it. You can see some further details in my answer to this post.
The other possible solution is to attack this from the Membership side of things; query for the latest membership matching the user in question and filter accordingly.

Related

Entity Framework Core Generating SQL With Ambiguous Column Names

I am using Entity Framework Core 2.2.6. I'm going to try and make this question concise and apologies in advance if it ends up being a wall of text.
The error I am seeing is an ambiguous column name in the SQL Entity Framework Core generates.
So my situation is this: I have two entities with a many-to-one relationship. The "parent" entity implements
an interface that has a property that is of type IChildEntity. Here are the interfaces:
public interface IParentEntity
{
IChildEntity Child { get; set; }
string Prop1 { get; set; }
string Prop2 { get; set; }
}
public interface IChildEntity
{
string ChildProp1 { get; set; }
string ChildProp2 { get; set; }
}
I am using ef core's fluent api and in order to set up the relationship between parent and child
I am using a concrete type of ChildEntity and defining a IChildEntity property to conform to the
interface and just passing things through to the concrete type:
public class ChildEntity : IChildEntity
{
public long ID {get; set;}
public string ChildProp1 { get; set; }
public string ChildProp2 { get; set; }
}
public class ParentEntity : IParentEntity
{
public long ID { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public long ChildID { get; set; }
// Navigation property so EF Core can create the relationship
public ChildEntity MappedChild { get; private set; }
// this is to adhere to the interface
// just pass this through to the backing concrete instance
[NotMapped]
public IChildEntity Child
{
get => MappedChild;
set => MappedChild = (ChildEntity)value;
}
}
Then in OnModelCreating I set up the relationship like so:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentEntity>()
.HasOne(e => e.MappedChild)
.WithMany()
.HasForeignKey(e => e.ChildID);
}
This works and the relationship gets set up as expected, however I am finding when I do a query it can generate
some SQL that can result in an ambigous column error in some database engines. Here is the example query:
MyContext.ParentEntity
.Include(p => p.MappedChild)
.Where(p => p.Prop1.Equals("somestring")
.FirstOrDefault()
The SQL that gets generated is similar to:
SELECT p."ID", p."ChildID", p."Prop1", p."Prop1", "p.MappedChild"."ID", "p.MappedChild"."ChildProp1", "p.MappedChild"."ChildProp2"
FROM "ParentEntity" AS p
INNER JOIN "ChildEntity" AS "p.MappedChild" ON p."ChildID" = "p.MappedChild"."ID"
WHERE p."Prop1" = 'somestring'
ORDER BY "p.MappedChild"."ID"
LIMIT 1
The problem here is we are selecting two columns with the name ID and not aliasing. Some databases will be ok with this
but some will not. A work around I can do for this is to do two separate queries to get the entity and the child entity:
var parent = MyContext.ParentEntity
.Where(p => p.Prop1.Equals("somestring")
.FirstOrDefault()
MyContext.Entry(parent).Reference(p => s.MappedChild).Load();
But this is less than ideal since it does multiple queries and is a bit less elegant than just using Include()
Because this seems like such a common use case and I couldn't find any bug reports against EF Core for this type of
behavior it is my suspicion that I am doing something wrong here that is resulting in EFCore not aliasing column names
for this type of query. I was thinking it could be the bit of trickery I have to do to ensure my entity implements it's interface
(this is something I can't due to constraints in the codebase and other integrations) but the more I look at it the less likely that
seems to me since we are directly dealing with the "mapped" property in EF related code and it's completely unaware of the interface.
My questions are - can anyone see something in my implementation that would cause this? Could anyone
suggest a better workaround than what I have here? Any advice here would be appreciated. Thanks much.
This is an old Entity framework bug with the Oracle company products bug including the MySQL database and Oracle database (12.1 and older).
I see the
ORA-00918: column ambiguously defined
error mostly when:
Selecting one entity with including parent entity.
Selecting one entity with value object own one command
This error appears when using Find, First, FirstOrDefault, Last, Single and all single entity selector commands.
I tested many solutions and check generated sql statement to find out a very unique way without any performance overhead:
// This the way of getting one entity from oracle 12.1 without throwing Oracle exception => ORA-00918: column ambiguously defined without any extra overhead
var entities = await dbSet.Where(x => x.Id == id).Take(1).ToListAsync();
var entity = entities.FirstOrDefault();
Another Sample:
var entities = await dbSet.OrderByDescending(x => x.Id).Take(1).ToListAsync();
var entity = entities.FirstOrDefault();
At the end of your IQueryable Linq add Take(1) and get all with .ToList() or .ToListAsync() to execute the statement and fetch a list with one record. Then use Enumerable Single Entity Selector to change the list to an entity.
That’s all.

Fluent NHibernate: Many to Many mapping on a single table

I have a Table of Companies, and I'm trying to build a matrix of competitors between them.
For example: McDonalds is a Competitor of Wendy's, and vice-versa.
Here's the mappings I've tried:
HasManyToMany(x => x.Competitors)
.ParentKeyColumn("IssuerID")
.ChildKeyColumn("CompetitorID")
.Table("Competitors")
.Cascade.All().Not.LazyLoad();
as well as:
Table("Issuer");
Id(x => x.Key).Column("Id").GeneratedBy.Assigned();
Map(x => x.Name).Length(1000);
HasManyToMany(x => x.Competitors).ParentKeyColumn("IssuerID").ChildKeyColumn("CompetitorID").Table("Competitors").Cascade.All().Not.LazyLoad();
When I add a competitor to an Issuer, I can see the correct mapping in the database.
So If I do:
McDonalds.AddCompetitor(Wendys);
I will see the correct data in the DB. I will also see the correct DB in the Entities when i get McDonalds using NHibernate.
But, if I return Wendy's from Nhibernate and look at the Competitors object:
Wendys.Competitors
I don't see McDonalds. I can understand why because Wendy's was added to McDonalds as a Child.
How can I modify this so I can view the competitors from both directions?
I came up with the following solution in my local tests. Actually I am not really satisfied as I would hope for a better support. Maybe there is. If someone finds it please post.
Having a store like this (shrinked down to the relevant parts of competitors):
public class Store
{
public virtual int Id { get; protected set; }
public virtual IList<Store> Competitors { get; set; }
}
I used the following mapping (same as you):
HasManyToMany(x => x.Competitors)
.ParentKeyColumn("Store_Id")
.ChildKeyColumn("Competitor_Id")
.Table("CompetitorsMapping");
As you state it does not work with this. But from the examples provided in the Git Repo of fluentnhibernate it wraps the add of the lists anyway.
The "FirstProject" example does something similar by adding an employee to a store. It first sets the store on the employee and then adds the employee to the Staff of the store (okay a bit different then). But it automatically takes care of dependencies.
So you could solve your problem by not setting directly but by wrapping the add and remove (From your sample code it looks like you already do that):
public class Store
{
public virtual void AddCompetitor(Store competitor)
{
if (!Competitors.Any(x => x.Id == competitor.Id))
{
competitor.Competitors.Add(this);
Competitors.Add(competitor);
}
}
public virtual void RemoveCompetitor(Store competitor)
{
competitor.Competitors.Remove(this);
Competitors.Remove(competitor);
}
}
What I especially don't like is the contains check on Competitors. Don't know how it performs and behaves. But without you can add the same competitor over and over. Making the list long.
I am new to fluentnhibernate. Maybe some setting could prevent the double adds automatically. I'd be interested in that setting.

NHibernate Filtered Child Collection Lazy Loaded even with eager fetch specified

Im trying to find out why a child collection is being returned without filtering even when eager loading the collection and the generated SQL is correct.
The fluent mappings for the classes are:
public class OptionIdentifierMap : ClassMap<OptionIdentifier>
{
public OptionIdentifierMap()
: base("OptionIdentifier")
{
//Id Mapping Removed
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None();
}
}
public class OptionPriceMap : ClassMap<OptionPrice>
{
public OptionPriceMap()
: base("OptionPrice")
{
//Id Mapping removed
References(x => x.Option)
.Column("OptionIdentifier_id")
.Cascade.None()
.ForeignKey("FK_OptionPrice_OptionIdentifier_id_OptionIdentifier_Id")
.Not.Nullable();
References(x => x.Increment)
.Column("PricingIncrement_id")
.Cascade.None()
.ForeignKey("FK_OptionPrice_PricingIncrement_id_PricingIncrement_Id")
.Not.Nullable();
Map(x => x.Price).Not.Nullable();
}
}
and PricingIncrement mapping
public class PricingIncrementMap : ClassMap<PricingIncrement>
{
public PricingIncrementMap()
: base("PricingIncrement")
{
Map(x => x.IncrementYear);
HasMany<OptionPrice>(x => x.Options)
.KeyColumn("PricingIncrement_id")
.Cascade.None().Inverse();
}
}
And the Entities are:
public class PricingIncrement : Entity
{
public PricingIncrement()
{
Options = new List<OptionPrice>();
}
public virtual int IncrementYear { get; set; }
public virtual IList<OptionPrice> Options { get; set; }
}
public class OptionPrice : Entity
{
public OptionPrice()
{
}
public virtual OptionIdentifier Option { get; set; }
public virtual PricingIncrement Increment { get; set; }
public virtual float Price { get; set; }
}
public class OptionIdentifier : Entity
{
public OptionIdentifier()
{
OptionPrices = new List<OptionPrice>();
}
public virtual IList<OptionPrice> OptionPrices { get; set; }
}
Im trying to query All the OptionIdentifier that have an optionprice value for an specific PricingIncrement.
The SQL Query that nhibernate generates from my criteria is:
SELECT this_.Id as Id37_4_,
.......
FROM OptionIdentifier this_ inner join OptionPrice op2_ on this_.Id = op2_.OptionIdentifier_id
inner join PricingIncrement i3_ on op2_.PricingIncrement_id = i3_.Id
WHERE (this_.IsDeleted = 0)
and this_.Id in (7)
and i3_.IncrementYear = 2015
The criteria I'm using to build this query is:
ICriteria pagedCriteria = this.Session.CreateCriteria<OptionIdentifier>()
.CreateAlias("OptionPrices", "op", JoinType.InnerJoin)
.CreateAlias("op.Increment", "i", JoinType.InnerJoin)
.SetFetchMode("op", FetchMode.Eager)
.SetFetchMode("i", FetchMode.Eager)
.Add(Restrictions.Eq("i.IncrementYear", 2015))
.Add(Expression.In("Id", idList.ToList<int>()))
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity);
When looking at SQL Profiler, the query executes and the result is correct, i get one row for each child in the OptionPrice table that matches the criteria, in my case one, from the available 4 rows that match the OptionIdentifier (there are 4 rows in PricingIncrement and 4 in OptionPrice one for each PricingIncrement for the OptionIdentifier_id 7)
But when i try to iterate the collection to get some values, for some reason nhibernate is loading the child collection, as if lazy load was specified, and loading the full 4 child rows. Reading the documentation FetchMode is supposed to fix this preventing nhibernate to lazy load child collections. Similar to a N+1 common issue.
I checked the SQL Profiler to see whats happening and nhibernate is generating queries without the original filter to fill the child collection when i try to access it. If i dont access the collection no query is generated.
Doing some testing i tried different join types and fetch modes, and so far the only way to iterate the collection without having hibernate load all the elements is to specify in the join type LeftOuterJoin, but this means something different.
I tried to search for issues similar but all of them say that eager loading should work, or mention that i should use filters. And so far i havent found any answer.
Any help is greatly appreciated.
I would like to share my approach, maybe not the answer...
I. avoid fetching one-to-many (collections)
When creating any kind of complex queries (ICriteria, QueryOver) we should use (LEFT) JOIN only on a start schema. I.e. on many-to-one (References() in fluent). That leads to expected row count from the perspective of paging (there is always only ONE row per root Entity)
To avoid 1 + N issue with collections (but even with many-to-one in fact) we have the NHiberante powerful feature:
19.1.5. Using batch fetching
NHibernate can make efficient use of batch fetching, that is, NHibernate can load several uninitialized proxies if one proxy is accessed (or collections. Batch fetching is an optimization of the lazy select fetching strategy)...
Read more here:
How to Eager Load Associations without duplication in NHibernate?
How to implement batch fetching with Fluent NHibernate when working with Oracle?
So, in our case, we would adjust mapping like this:
public PricingIncrementMap()
: base("PricingIncrement")
{
Map(x => x.IncrementYear);
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None()
.Inverse() // I would use .Inverse() as well
// batch fetching
.BatchSize(100);
}
II. collection filtering
So, we managed to avoid 1 + N issue, and we also query only star schema. Now, how can we load just filtered set of items of our collection? Well, we have native and again very powerful NHibernate feature:
18.1. NHibernate filters.
NHibernate adds the ability to pre-define filter criteria and attach those filters at both a class and a collection level. A filter criteria is the ability to define a restriction clause very similiar to the existing "where" attribute available on the class and various collection elements...
Read more about it here:
how to assign data layer-level filters
Limit collection to retrieve only recent entries for readonly entity
So in our case we would define filter
public class CollFilter : FilterDefinition
{
public CollFilter()
{
WithName("CollFilter")
.WithCondition("PricingIncrement_id = :pricingIncrementId")
.AddParameter("pricingIncrementId",NHibernate.Int32);
}
}
And we would need to extend our mapping again:
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None()
.Inverse()
// batch fetching
.BatchSize(100)
// this filter could be turned on later
.ApplyFilter<CollFilter>();
Now, before our query will be executed, we just have to enable that filter and provide proper ID of the year 2015:
// the ID of the PricingIncrement with year 2015
var pricingIncrementId thes.Session
.QueryOver<PricingIncrement>()
.Where(x => x.IncrementYear == 2015)
.Take(1)
.Select(x => x.ID)
.SingleOrDefault<int?>();
this.Session
.EnableFilter("CollFilter")
.SetParameter("pricingIncrementId", pricingIncrementId);
// ... the star schema query could be executed here
III. Sub-query to filter root entity
Finally we can use sub-query, to restrict the amount of root entities to be returned with our query.
15.8. Detached queries and subqueries
Read more about it here:
Query on HasMany reference
NHibernate Criteria Where any element of list property is true
so, our subquery could be
// Subquery
var subquery = DetachedCriteria.For<OptionPrice >()
.CreateAlias("Increment", "i", JoinType.InnerJoin)
.Add(Restrictions.Eq("i.IncrementYear", 2015))
.SetProjection(Projections.Property("Option.ID"));
// root query, ready for paging, and still filtered as wanted
ICriteria pagedCriteria = this.Session.CreateCriteria<OptionIdentifier>()
.Add(Subqueries.PropertyIn("ID", subquery))
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity);
Summary: We can use lot of features, which are shipped with NHibernate. They are there for a reason. And with them together we can achieve stable and solid code, which is ready for further extending (paging at the first place)
NOTE: maybe I made some typos... but the overall idea should be clear

NHibernate + LINQ - efficient work with related collections

I'm quite new to LINQ, but I already know the basic difference between IQueryable and IEnumerable (i.e. conditions applied to the first one are likely to be executed by the remote engine, in my case - translated to SQL).
Now, all the NHibernate tutorials I've ever seen use IList<T> for related collections.
I'd like to know if there's any way to use IQueryable instead, so the filtering could be efficient.
Unfortunately, if I try to use IQueryable<T> as the property type for related collection, it causes an error:
Unable to cast object of type
'NHibernate.Collection.Generic.PersistentGenericBag1[....PersonalDocument]'
to type 'System.Linq.IQueryable
Therefore to keep the filtering of related collections SQL-side, I need to query the related type directly providing the extra condition which "binds" the relation. This is quite inconvenient, and I'd like to avoid this (since the relationship condition is already declared ...)
To clarify a bit:
I have two models, as follows:
public partial class Person
{
public virtual int Id { get; set; }
// ....
public virtual IEnumerable<PersonalDocument> Documents { get; set; }
}
public class PersonalDocument
{
// ...
public virtual int Id { get; set; }
public virtual Person Owner { get; set; }
public virtual string Type { get; set; }
}
and the mapping code for Person:
HasMany<DomainModel.PersonalDocument>(x => x.Documents)
.KeyColumn("Owner_Id")
.Inverse()
.Cascade.Delete()
.AsBag();
and the query code
personInstance.Documents.Where(d => d.Type == "xx").Count()
is being translated to non-optimal SQL (no "type" filter applied to SQL, count done client-side)
while this one:
s.Query<PersonalDocument>().Count(x => x.Owner == personInstance && x.Type == "xx");
translates to a nice SQL:
is there any way to write DRY LINQ queries like the first one, yet have resulting SQL optimal like the second one?
There is no such feature in NHibernate core.
See https://nhibernate.jira.com/browse/NH-2319 and http://www.nuget.org/packages/NHibernate.CollectionQuery.
Use IList/ISet/IDictionary!
You can create a collection for each document type in the class Person. In the mapping you can do:
HasMany(x => x.Documents)
.KeyColumn("Owner_Id")
.Inverse()
.Cascade.Delete()
.Where("Type=xx")
.AsBag();
I do not like this solution. I also do not like how your Product class is partial. If I were you I would try the WHERE suggestion and after everything is working I would take a look at my design.
Also take a look at NHibernate inheritance strategies. Actually I think this is exactly what you want:
http://www.nhforge.org/doc/nh/en/#inheritance
http://ayende.com/blog/3941/nhibernate-mapping-inheritance
http://lostechies.com/jimmybogard/2008/08/27/strategies-and-discriminators-in-nhibernate/

NHibernate recreates every associated item

I have a very simple object models.
public class Contact
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Device Device { get; set; }
public virtual IList<string> Numbers { get; set; }
public Contact()
{
Numbers = new System.Collections.Generic.List<string>(3);
}
}
As you can see, the class Contact has an association with Numbers, which is a list of strings.
Here's the mapping:
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
References(x => x.Device, "DeviceId");
Table("Contacts");
HasMany(x => x.Numbers)
.Table("ContactNumbers")
.Element("Number")
.KeyColumn("ContactId")
.LazyLoad()
.Cascade.All()
.Not
.Inverse();
Note that I can't and don't want the collection to be inverse=true, because it's just a collection of string. This means that Contact is responsible for updating Numbers entries.
Now my problem is that, whenever I try to add a new number to an existing Contact, it deletes all associated numbers and recreates them individually. Isn't NHibernate smart enough to detect changes and update only changed items?
I think there should be a simple solution for my problem but don't know what.
Any help would be appreciated.
This is actually documented in NHibernate's documentation.
Bags are the worst case. Since a bag permits duplicate element values
and has no index column, no primary key may be defined. NHibernate has
no way of distinguishing between duplicate rows. NHibernate resolves
this problem by completely removing (in a single DELETE) and
recreating the collection whenever it changes. This might be very
inefficient.
Try using an <idbag> mapping instead, and create a surrogate primary key for that table. Unfortunately, looks like <idbag> is not yet supported in FluentNHibernate.
Also, take a look at other collection mapping options.

Categories

Resources