Having been comfortably using Entity Framework for many years, I've just been thrown in the deep end with a project that uses NHibernate and I'm really struggling. The documentation is sparse and unhelpful if you're working with it for the first time, and most tutorial and example sites are out of date - I understand it changed significantly in v3?
Normally, I learn things best when trying to work with them, so I jumped in and tried to see what I could do. But I've hit a bug in this pre-existing function (none of this code is mine):
public IDictionary<long, string> GetSeriesFilterData(string userId)
{
Series seriesAlias = null;
Event eventAlias = null;
Session sessionAlias = null;
Dealership dealershipAlias = null;
var query = _repository.Session.QueryOver(() => seriesAlias)
.Where(() => !seriesAlias.IsArchived);
var dealershipIds = QueryOver.Of<ApplicationUserDealership>()
.Where(x => x.ApplicationUser_Key == userId)
.SelectList(list => list.SelectGroup(x => x.Dealership_Id));
dealershipIds.Where(x => x.Dealership_Id == dealershipAlias.Id);
query
.JoinAlias(() => sessionAlias.Dealership, () => dealershipAlias, JoinType.LeftOuterJoin)
.WithSubquery.WhereExists(dealershipIds);
var results = query.SelectList(x => x
.SelectGroup(() => seriesAlias.Id)
.SelectGroup(() => seriesAlias.Name))
.List<object[]>()
.ToDictionary(x => (long) x[0], x => (string) x[1]);
return results;
}
The exception, thrown when collecting the result, is:
An exception of type 'NHibernate.QueryException' occurred in NHibernate.dll but was not handled in user code
Additional information: could not resolve property: sessionAlias
My suspicion is that this is because dealershipIds is empty, but I'm struggling to prove that this is the case. Working with Entity Framework, it's possible to see the results of a query during debugging by unpacking its object graph. However, I can't seem to do that in NHibernate.
Is it possible to see the results of a query fragment via debugging, or do I have to pull it out with a Select statement?
The QueryOver you've posted seems to be missing the part where the
sessionAlias alias is assigned to something.
As it's being done for the others (except eventAlias which seems completely unused) the alias needs to be assigned to a property/path on the entity class being queried via JoinAlias/JoinQueryOver or to the entity itself as it's done on the QueryOver creation with seriesAlias.
Then, that alias (variable) can be used in Where, OrderBy, etc.
For example, supposing that Session is an entity referenced from a property of Series (no clue regarding your actual entity model) the following would fix your problem as it will bind sessionAlias to that property:
// This binds the alias to the property.
query.JoinAlias(s => s.Session, () => sessionAlias);
// This is the same as above, but uses the previously defined alias for the main entity,
// just to show how aliases can be used.
query.JoinAlias(() => seriesAlias.Session, () => sessionAlias);
Related
I have this simple EF Core query which throws an exception, to my surprise:
var test = await _context.GroupMessages
.Select(m => new MinimalChannelMessage()
{
id = m.Id,
er = m.Emojis.ToDictionary(e => e.Emoji, e => e.Users.Select(u => u.UserId))
})
.ToListAsync();
As you can see from the query, the tables are GroupMessages -> Emojis -> N-to-M table with UserId
The exception I get is:
When called from 'VisitLambda', rewriting a node of type 'System.Linq.Expressions.ParameterExpression' must return a non-null value of the same type. Alternatively, override 'VisitLambda' and change it to not visit children of this type.
I haven't been able to figure out why this simple query fails, or what the exception message is trying to tell me. I'm happy for any help!
Thanks
While the suggested bug report https://github.com/dotnet/efcore/issues/18440 might be related, I had trouble finding an alternative amongst those listed. I tried...
er = new Dictionary<string, string[]>(m.Emojis.AsEnumerable().Select(e => new KeyValuePair<string, string[]>(e.Emoji, e.Users.Select(u => u.UserId).ToArray())))
er = m.Emojis.AsEnumerable().ToDictionary(e => e.Emoji, e => e.Users.Select(u => u.UserId).ToArray())
None of those two worked for me. So I ultimately had to just include the whole tree, then do the ToDictionary in memory...
Given something like:
Post posts = null;
Author author = null;
blog = session.QueryOver<Blog>()
.Where(x => x.Id == 1)
.JoinAlias(x => x.Posts, () => posts)
.JoinAlias(() => posts.Author, () => author)
.SingleOrDefault();
In NHibernate, the above value can be to variables passed into the 2nd argument of the JoinAlias
I've tried to figure this out and the closest I get is casting the Body to MemberExpression followed by casting the expression to ConstantExpression but that creates .Value which is readonly, using reflection to assign the member doesn't modify the reference passed in.
How does NH achieve this?
It does not.
This is just syntax. Nothing is assigned neither to posts nor to author. Those variables are here only to allow using them as aliases in later restrictions, such as in following example taken from here:
Cat catAlias = null;
Kitten kittenAlias = null;
IQueryOver<Cat,Cat> catQuery =
session.QueryOver<Cat>(() => catAlias)
.JoinAlias(() => catAlias.Kittens, () => kittenAlias)
.Where(() => kittenAlias.Name == "Tiddles");
It allows QueryOver to translate all that to a working SQL query. (I guess actually HQL first, which gets then converted to SQL.) But after execution, you should find your variables untouched, and still null.
That's my code:
ProjetoTipoCargaModelo projAux =
dbContext.ProjetoTipoCargaDbSet.Find(idProjetoTipoCarga);
ICollection<ProjetoTipoCargaRegraModelo> regras =
projAux.ListaRegra.Where(x => x.Ativo).ToList();
IQueryable<ProjetoTipoCargaRegraModelo> pr =
dbContext.ProjetoTipoCargaDbSet.Select(
x => regras.FirstOrDefault(y => y.IdProjetoTipoCarga == x.IdProjetoTipoCarga));
var projetoCompleto = pr.
Include(x => x.ListaRegraLiberacaoInicioViagem).
Include(x => x.ListaRegraTecnologiaAceita).
Include(x => x.RegraAreaSombra).
Include(x => x.RegraAtuadorNecessario)
It's showing an error at first include, but I'm trying to do it on Iquerable object!
What's wrong where?
My problem is make this include in a filtered set of results.
[Edit]
Error:
Cannot convert lambda expression to type 'string' because it is not a delegate type
It's not a runtime error, it's a compilation error.
[Edit 2]
My usings:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
[Edit 3]
Answer:
ICollection<ProjetoTipoCargaRegraModelo> regras = projAux.ListaRegra.Where(x => x.Ativo).ToList();
IQueryable<ProjetoTipoCargaModelo> pr = dbContext.ProjetoTipoCargaDbSet.Where(x => x.IdProjetoTipoCarga == regras.FirstOrDefault(y => y.IdProjetoTipoCarga == x.IdProjetoTipoCarga).IdProjetoTipoCarga);
var projetoCompleto = pr.
Include(x => x.ListaRegraLiberacaoInicioViagem).
Include(x => x.ListaRegraTecnologiaAceita).
Include(x => x.RegraAreaSombra).
The .Include() method only works on ObjectQuery<TEntity>
Try:
context.EntitySet.Include(...).Select(...)
instead of:
context.EntitySet.Select(...).Include(...)
or use an extenion method like this:
public static class MyExtensions
{
public static IQueryable<TEntity> Include<TEntity>(
this IQueryable<TEntity> query, string path)
{
var efQuery = query as ObjectQuery<TEntity>;
if (efQuery == null)
return query;
return efQuery.Include(path);
}
}
or better yet, use the already available extension method that supports lambda expressions instead of strings as paths.
Also, do not use so many includes unless most are 1:1 or :1 relationships, 1: (or :) relationships greatly increase the IO from the database, resulting in bad performance.
Consider using multiple queries with .Future() to enable a single access to the database instead.
It's not related to the Includes.
You can't use regas in a LINQ-to-entities query because it's an ICollection. EF can't translate that into SQL.
Your "answer" can't possibly work with EF. The object regas is an in-memory list of Regra entities (I guess). If you use that directly in...
var pr = dbContext.ProjetoTipoCargaDbSet
.Where(x => x.IdProjetoTipoCarga == regras.FirstOrDefault(y => y.IdProjetoTipoCarga == x.IdProjetoTipoCarga).IdProjetoTipoCarga);
...you should get an exception like
Unable to create a constant value of type 'Regra'. Only primitive types or enumeration types are supported...
But, boy, what a tortuous way to get where you want to be! First you get a ProjetoTipoCargaModelo object by an idProjetoTipoCarga. Then you fetch its active Regras. Then you basically use the IdProjetoTipoCarga values of the Regras to see if one of them is equal to the original idProjetoTipoCarga and if so, you use its value to get a ProjetoTipoCargaModelo object.
If you remove all the redundancies, what's left is:
var pr = dbContext.ProjetoTipoCargaDbSet
.Where(x => x.IdProjetoTipoCarga == idProjetoTipoCarga
&& x.ListaRegra.Any(r => r.Ativo));
I you us this LINQ statement, you append your includes to pr.
I'm having issues trying to reuse ProjectionLists in NHibernate QueryOvers. I can't work out how to reuse things for different root entities.
Object model is roughly represented as:
Breakfast one to many Pastry many to zero-or-one Coffee
The two separate queries are roughly:
session.QueryOver<Breakfast>()
.Where(b => b.Id == searchId)
.Inner.JoinQueryOver(b => b.Pastries, () => pastry)
.Left.JoinAlias(p => p.Coffee, () => coffee)
.Select(projections)
.TransformUsing(Transformers.AliasToBean<TargetDto>())
.List<TargetDto>();
session.QueryOver<Coffee>()
.Where(c => c.Id == searchId)
.Inner.JoinQueryOver(c => c.Pastries, () => pastry)
.Select(projections)
.TransformUsing(Transformers.AliasToBean<TargetDto>())
.List<TargetDto>();
The common projections I'm trying to reuse looks like this:
var projections = Projections.ProjectionList()
.Add(Projections.Property(() => pastry.Name, () => target.PastryName))
.Add(Projections.Property(() => coffee.Name, () => target.CoffeeName));
These projections, using the aliases, work fine for the first query (root: Breakfast), because they're not trying to pull off properties that are on that root entity. On the second query (root: Coffee), it explodes saying it can't find 'coffee.Name' on Coffee, because it doesn't like the alias. The QueryOver(() => coffee) syntax doesn't help because it doesn't actually register 'coffee' as an alias, it just uses it for type inference. Oh bloody hell that was the problem. There's a stupid piece of application infrastructure that is breaking the alias syntax to not actually use the alias version underneath.
The second query wants the projections to look like:
var projections = Projections.ProjectionList()
.Add(Projections.Property(() => pastry.Name, () => target.PastryName))
.Add(Projections.Property<Coffee>(c => c.Name, () => target.CoffeeName));
However this is now incompatible with the first query.
Is there any way of making this work, so the I can project properties without knowing what the root entity type is?
I think all you need to do is assign the coffee alias in the session.QueryOver<Coffee> call:
Coffee coffee = null;
session.QueryOver<Coffee>(() => coffee)
/* etc */
The below might be completely unrelated to what you're doing, but I figured I'd include it in case anyone else is writing code that passes around QueryOver aliases.
I'd add a word of caution-- reusing aliases across different queries like this can be a little dangerous.
NHibernate takes the expression () => coffee and grabs the name of the alias you're using from the expression (in this case, "coffee") and then uses it in the generated SQL as an alias. This means that depending on how your code is structured, shared projections like this could break if alias names change.
For example, say you had the following method to return some shared projections:
public ProjectionList GetSharedProjections()
{
Coffee coffee = null;
TargetDTO target;
var projections = Projections.ProjectionList()
.Add(Projections.Property(() => coffee.CoffeeName)
.WithAlias(() => target.CoffeeName));
return projections;
}
Then you had some code calling your helper method:
session.QueryOver<Coffee>(() => coffee)
.Select(GetSharedProjections());
Everything will work fine-- as long as your aliases match. The second anyone changes either of the aliases though, the query will fail.
You might be tempted to pass in an alias to a method like this:
public ProjectionList GetSharedProjections(Coffee coffeeAlias)
{
/* Same as above except with "coffeeAlias"
}
And then pass in your alias:
session.QueryOver<Coffee>(() => coffee)
.Select(GetSharedProjections(coffee));
But this won't work either. Remember that NHibernate is grabbing the name of the alias and using it directly in the generated SQL. The above code will try to use both "coffee" and "coffeeAlias" in the generated SQL and will fail.
One way to properly do this (without just hoping nobody changes alias names) is to pass around expressions and use those to reconstruct property names with the correct aliases.
You'd create a helper method that builds property access using an alias and a property:
public static PropertyProjection BuildProjection<T>(
Expression<Func<object>> aliasExpression,
Expression<Func<T, object>> propertyExpression)
{
string alias = ExpressionProcessor.FindMemberExpression(aliasExpression.Body);
string property = ExpressionProcessor.FindMemberExpression(propertyExpression.Body);
return Projections.Property(string.Format("{0}.{1}", alias, property));
}
Then, you could change the GetSharedProjections method to take an alias in the form of an expression:
public ProjectionList GetSharedProjection(Expression<Func<Coffee>> coffeeAlias)
{
TargetDTO target = null;
var projections = Projections.ProjectionList()
.Add(BuildProjection<Coffee>(coffeeAlias, c => c.CoffeeName))
.WithAlias(() => target.CoffeeName);
}
Now, calling your method would look like this:
session.QueryOver<Coffee>(() => coffee)
.Select(GetSharedProjections(() => coffee));
When someone changes your alias name you're covered. You can also safely use this method among many queries without worrying about what the name of the alias variable actually is.
Disclaimer: The following is a link to my personal blog
You can find more information about building QueryOver queries this way here.
I have a situation where I am calling an entity and putting in two includes in the ria services call.
public IQueryable<Position> GetPositions(int programID)
{
return _positionRepository.All()
.Where(x => x.ProgramID == programID)
.Include("RecPositions.Person");
}
Id like to get a handle on the Person entity on the front end. I have this working..the code below gives me a handle on the recPositions and in the intellisence I can see the Person object. id like to abstract that entity.
var test = _allRec.Select(x => x.RecPositions).ToList();
test now has my RecPosition...but i want to know how to write a lambda express so i can get a handle on the person object.
I came up with this..does anyone have any objections to this or a better way..
var test = _allRec.SelectMany(x => x.RecPositions)
.Select(p => p.Person)
.ToList();
this seems to give me what I want.