Lambda expression with two navigation properties - c#

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.

Related

Can you see NHibernate results mid-query during debugging in Visual Studio?

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);

LINQ using Automapper for a third level property

I have three entity framework objects that are nested - see image below. Also, I have generated similar objects as DTO to map them.
So my question is, as the companyDocument object has a list of ListCompanyDocumentsType, when I execute a query to pick all companies, it returns a list of Companies and its Documents plus a list of documents types with a list of all documents that each type has within the database (like a looping).
Is there any way to return the companyDocument with only one document type? Or I have designed it incorrectly? To solve this problem, I've used a mapped object and a for each that returns the type name within a non-mapped DTO property. So it is working, but I'm not sure if it is the correct way.
Also, I've tried to use the EF .include() to go up to the ListCompanyDocumentTypes, but it is still returning all documents that each type has.
var config = new MapperConfiguration(cfg => { cfg.CreateMap<Company, CompanyDto>(); cfg.CreateMap<CompanyDocument,CompanyDocumentDto>();});
var mapper = config.CreateMapper();
var newDtoTest = mapper.Map<List<CompanyDto>>(companiesReturn);
var db = new entities();
foreach (var companyDto in newDtoTest)
{
foreach (var companyDtoCompanyDocument in companyDto.CompanyDocuments)
{
companyDtoCompanyDocument.dtoTypeName = (await db.ListCompanyDocumentsTypes.FirstOrDefaultAsync(p=> p.id.Equals(companyDtoCompanyDocument.typeId))).typeName;
}
}
Thank you,
You should use ForMember(),using this method you will be able to set the appropriate value for the specific field:
cfg.CreateMap<Company, CompanyDto>()
.ForMember(dest => dest.dtoTypeName, opt => opt.MapFrom(src => src.CompanyDocuments.ListCompanyDocumentsTypes.FirstOrDefault().typeName));
And companiesReturn should be a IQueryable<Company> type, so your sample method GetAll Company need include CompanyDocuments and ListCompanyDocumentsTypes and should look like:
public IQueryable<AttributeElement> GetAll()
{
return CompanyRepository.GetQueryable()
.Include(d => d.CompanyDocuments)
.ThenInclude(d => d.ListCompanyDocumentsTypes);
}
Automapper has easy mapping, you do not need to use additional loops, everything can be set up in configuration class.

Why can't I use include?

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.

QueryOver ProjectionList with different root entity types

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.

How to navigate navigation properties using Linq and a repository pattern?

Using the Geneic Unit of Work and Repository Framework, I am trying to navigate navigation properties with Linq to Entities.
In my controller, I am using a repository pattern and unit of work. In this controller, the _repository is Websites.
Given the following three tables:
I am trying to get the Website entity, as well as a distinct list of ContentTypes.Description for all SourceUrls for a specific WebsiteID and UserId.
I can get the initial part of my data:
var website = _repository
.Query()
.Select()
.Single(u => u.UserId == userId && u.WebsiteGuid == websiteGuid);
I have had several messy attempts, including something like this:
var website = _repository
.Query()
.Select()
.Single(u => u.UserId == userId && u.WebsiteGuid == websiteGuid)
.SourceUrls.Any(s => s.ContentTypeId == s.ContentType.ContentTypeId)
.Select(new ContentType());
The framework gives me the ability to write out a SQL query through .SelectQuery(...), however I'm trying to avoid this.
I can create a new DTO and cast it to this as well.
Suggestions on how to get this Linq query to work?
Thanks.
--Update--
Trying this through the Website entity seems to be difficult as there is no direct navigation property Websites--ContentTypes.
So, Starting from SourceUrls, I tried the following:
var rep2 = _unitOfWork.Repository<SourceUrl>()
.Query(q => q.UserId == userId)
.Include(i => i.ContentType.Description.Distinct())
.Include(i => i.Website)
.Select().Where(w => w.Website.WebsiteGuid == websiteGuid).ToList();
However I'm getting this new error:
"The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties."
I also tried doing the linking through the .Select() but not quite there...
I don't have your data model to play with but this appears to be working on my local system:
using System.Linq;
using System.Data.Entity;
public class Class1
{
public Class1()
{
var website = _repository
.Where(w => w.UserId == userIdArg && w.WebsiteGuidArg == websiteGuidArg)
.
}
}
Your model may give you varying amounts of success with that Distinct() but the important things is that you can use include to make sure you get the collections loaded.
I've made this answer community wiki in case any new discoveries require a change to it.

Categories