Entity Framework Join inner Include - entity-framework

What is wrong ?
var tests = _repo.Tests.Include(a => a.Answers.Join(_repo.Questions, answer => answer.QuestionNumber, question => question.QuestionNumber, (answer, question) => new { Answer = answer, Question = question })).Where(u => u.User.UserName == username);
error CS0411: The type arguments for method 'Enumerable.Join(IEnumerable, IEnumerable, Func, Func, Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

You can use ThenInclude to join the other tables like this.
var tests = _repo.Tests
.Include(a => a.Answers)
.ThenInclude(q => q.Questions)
.Where(u => u.User.UserName == username)
.ToList();

Related

How to write EF query to table with multiple foreign keys without putting it at the start of the query?

EDIT:
I'm one step closer to what I'm after. I added a navigation property to Skills which allowed me to ThenInclude the Skill Responses as seen below.
My only problem now is that I'm not sure how to filter the Skill Responses such that it only includes those for the current UserAssessment. How can I do this?
var userAssessmentQuery = AppDbContext.UserAssessments
.Include(o => o.User)
.Include(o => o.Assessment)
.ThenInclude(o => o.Categories)
.ThenInclude(o => o.Competencies)
.ThenInclude(o => o.Skills)
.ThenInclude(o => o.Responses.Where(o => o.UserAssessmentId == ???);
Original Post:
The first query below is the working code I have and then the following two are an example that would technically return the data I want but not in a friendly format and then an example that won't compile but might work if I knew what steps to take to make it work.
I can think of ways to accomplish what I need to do, however I'm certain there is/are one or more "proper/correct" way(s) to do this in EF of which I am unaware. That is, I'm sure I could do two queries and then manually put the data together how I want it but that doesn't seem very smart.
public async Task<UserAssessment> GetFirstAssessment()
{
// This works and is essentially what I want, but I also need to add users' responses to skill questions
var userAssessmentQuery = AppDbContext.UserAssessments
.Include(o => o.Assessment)
.ThenInclude(o => o.Categories)
.ThenInclude(o => o.Competencies)
.ThenInclude(o => o.Skills);
// This technically could work but totally ruins the json structure I would like to output
var userAssessmentQueryWithResponses = AppDbContext.SkillResponses
.Include(o => o.UserAssessment)
.ThenInclude(o => o.Assessment)
.ThenInclude(o => o.Categories)
.ThenInclude(o => o.Competencies)
.ThenInclude(o => o.Skills);
// This is closer to what I want, but doesn't work because I don't have anything directly on the AssessmentSkill class/table indicating
// it is related to the SkillResponse class/table. It's also problematic because the responses also has a foreign key to the UserAssessment
// which has to be taken into account as well.
//var userAssessmentQueryWithResponses = AppDbContext.UserAssessments
// .Include(o => o.Assessment)
// .ThenInclude(o => o.Categories)
// .ThenInclude(o => o.Competencies)
// .ThenInclude(o => o.Skills)
// .ThenInclude(o => o.SkillResponse);
var assessment = await userAssessmentQuery.FirstAsync();
return assessment;
}
For selecting appropriate structure you have to use custom projection. In this case you do not need Include at all.
This is just sample how to process entities:
var query =
AppDbContext.UserAssessments.Select(ua => new
{
id = ua.Id,
assessments = ua.Assesments.Select(a => new
{
id = a.Id,
tentantId = a.TentantId,
title = a.Title,
categories = a.Categories.Select(c => new
{
id = c.Id,
assesmentId = c.AssesmentId,
title = c.Title,
quote = c.Quote,
competencies = c.Competencies.Select(com => new
{
id = com.Id,
assessmentCategoryId = com.AssessmentCategoryId,
title = com.Title,
question = com.Question,
skills = com.Skills.Select(s => new {...}).ToArray()
...
}).ToArray()
}).ToArray()
});
var result = await query.FirstAsync();

EF Core: The LINQ expression could not be translated

I am trying to implement a query in EF Core in which I need to get data where any name in a string array is contained by the name of the data object. Here is the code sample:
var searchKeys = search.Split(' ');
var objects = _db.Objects
.Where(o => searchKeys.Any(k => o.name.Contains(k))))
.OrderBy(o => o.Name)
.Select o
But the query cannot be translated resulting in the following error:
The LINQ expression 'DbSet
.Where(o => __searchKeys_1
.Any(k => __Functions_2
.Contains(
_: o.Name,
propertyReference: k)))' could not be translated. Either rewrite the query in a form that can be translated, or switch
to client evaluation explicitly by inserting a call to either
AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See
https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
How can I contruct the query to fix the error?
For some reason, I could not get Obie's answer to work for me in .Net Core 3.1. Luckily, the LinqKit NuGet package has been upgraded for .Net Core. So I defined this extension method:
// Where any search predicates are true.
public static IQueryable<T> WhereAny<T>(this IQueryable<T> q, params Expression<Func<T, bool>>[] predicates)
{
var orPredicate = PredicateBuilder.New<T>();
foreach (var predicate in predicates)
{
orPredicate = orPredicate.Or(predicate);
}
return q.AsExpandable().Where(orPredicate);
}
And then you would use it like this:
var searchKeys = search.Split(' ');
var predicates = searchKeys.Select(k => (Expression<Func<MyObject, bool>>)(x => x.Name.Contains(k)));
var objects = _db.Objects
.WhereAny(predicates.ToArray())
.OrderBy(o => o.Name);
This works for me:
var searchKeys = search.Split(' ');
var objects = _db.Objects
.Where(o => searchKeys.Any(k => o.Name.Contains(k)))
.OrderBy(o => o.Name)
.Select(o => o);

The specified type member 'Date' is not supported in LINQ to Entities. DbFunctions.TruncateTime()

Question question =
db.Questions
.Where(q => DbFunctions.TruncateTime(q.Day.DayDate) == DbFunctions.TruncateTime(DateTime.Now.Date))
.Where(q => q.Order == id)
.FirstOrDefault();
When I try to run the above statement, I get the following error:
The specified type member 'Date' is not supported in LINQ to Entities
I thought that DbFunctions.TruncateTime() method would solve this problem as suggested in many posts I have seen, however I still get the error. I also tried to just apply the method to the database value but I still get the same error.
Question question =
db.Questions
.Where(q => DbFunctions.TruncateTime(q.Day.DayDate) == DateTime.Now.Date)
.Where(q => q.Order == id)
.FirstOrDefault();
You need to pull DateTime into variable before LINQ:
var dateOfNow = DateTime.Now.Date;
Question question =
db.Questions
.Where(q => DbFunctions.TruncateTime(q.Day.DayDate) == dateOfNow)
.Where(q => q.Order == id)
.FirstOrDefault();

"Unable to create a constant value of type .. Only primitive types are supported ..'' in EF query?

I have a work around for this issue, however I would appreciate it if someone could explain why this is happening and how I would design this for large datasets where my work around would not be viable.
The full error is:
Unable to create a constant value of type 'THPT_Razor.Models.WinType'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
and I am using EF v4.0.
The commented lines are the offending code and the work around is the "For loop"
Thank you in advance.
List<WinType> _atype = db.WinTypes.Where(wt => wt.IsWin == false).ToList();
List<WinType> _wtype = db.WinTypes.Where(wt => wt.IsWin == true).ToList();
string test = _wtype.Where(wt => wt.Value ==0).Select(wt => wt.Description).SingleOrDefault();
List<WinCheckDetails> wcd = db.Wins.Include("UserProfiles").Where(w => w.venueLogId == logid).Select(w => new WinCheckDetails
{
//awarddesc = w.atypeid.HasValue ? _atype.Where( wt=> wt.Value == w.atypeid).Select(wt => wt.Description).SingleOrDefault():string.Empty,
//windesc = _wtype.Where(wt => wt.Value == w.typeid).Select(wt => wt.Description).Single(),
atypeid = w.atypeid,
typeid = w.typeid,
WinId = w.WinId,
other = w.other,
posterid = w.posterid,
confirmed = w.confirmed,
posttime = w.posttime,
game = w.game,
playerid = w.UserProfile.PlayerID,
firstname = w.UserProfile.FirstName,
lastname = w.UserProfile.LastName,
fullname = w.UserProfile.FirstName + " " + w.UserProfile.LastName
}).OrderBy(o => o.game).ToList();
foreach (WinCheckDetails wc in wcd)
{
wc.awarddesc = _atype.Where(wt => wt.Value == wc.atypeid).Select(wt => wt.Description).SingleOrDefault();
wc.windesc = _wtype.Where(wt => wt.Value == wc.typeid).Select(wt => wt.Description).SingleOrDefault();
}
_atype and _wtype are lists of WinType in memory because you are applying ToList() to the queries. With respect to database queries they are collections of constant values because to perform the query in the database they have to be transmitted to the database server as the values they are in memory. EF doesn't support to transfer such constant values or collections of values from memory to the database unless they are values of primitive types (int for example). That's the reason why you get an exception.
Did you try to use _atype and _wtype as IQueryable instead of lists:
IQueryable<WinType> _atype = db.WinTypes.Where(wt => !wt.IsWin);
IQueryable<WinType> _wtype = db.WinTypes.Where(wt => wt.IsWin);
List<WinCheckDetails> wcd = db.Wins
.Where(w => w.venueLogId == logid)
.Select(w => new WinCheckDetails
{
awarddesc = w.atypeid.HasValue
? _atype.Where(wt=> wt.Value == w.atypeid)
.Select(wt => wt.Description).FirstOrDefault()
: string.Empty,
windesc = _wtype.Where(wt => wt.Value == w.typeid)
.Select(wt => wt.Description).FirstOrDefault(),
// ... (unchanged)
}).OrderBy(o => o.game).ToList();
I have removed the Include because it will be ignored anyway when you perform a projection with Select. Also I have replaced SingleOrDefault and Single by FirstOrDefault because both are not supported in a projection (and First neither), only FirstOrDefault is supported.
I am not sure if that will work. But it should remove your exception (but maybe you'll get another one...).

In Linq to Entities can you convert an IQueryable into a string of SQL?

eg.
var result = myObject.Where(x => x.prop == 5);
string s = result.toSQL();
Result:
s is "SELECT * FROM [myObjects] WHERE prop = 5"
If it's IQueryable/ObjectQuery you can use ToTraceString. If it's IDbSet/DbSet you can use ToString directly.
Using EF 6 I could not get .ToString() to return SQL for something similar to:
db.Entry(parent)
.Collection(p => p.Children)
.Query()
.Where(c => c.Active)
.Load();
Then I remembered you can log it to debug output with:
db.Database.Log = (entry) => System.Diagnostics.Debug.WriteLine(entry);
Just set above before loading queries (duh) :)
db here is an instance of some DbContext derived class.