EF 5 union not working as expected - c#

I have a linq query involving the following 3 entities:
public class LandPoint
{
...
public OlsonTimeZone TimeZone { get; set; }
}
public class OlsonTimeZone : TimeZone
{
...
public virtual ICollection<WindowsTimeZone> Windows { get; set; }
}
public class WindowsTimeZone : TimeZone
{
...
public virtual ICollection<OlsonTimeZone> Olson { get; set; }
}
So a LandPoint has an OlsonTimeZone which has zero or more WindowsTimeZones.
What I am trying to do is get the WindowsTimeZone name (prefixed by 'Windows:') if the OlsonTimeZone has any WindowsTimeZones or the OlsonTimeZone name (prefixed by 'Olson:') as a fall back along with information about the point itself.
What I have written is:
return db.LandPoints.Where(x => x.GeoNameID == ID).Take(1).Select(x => new LandPoint
{
TimeZone = x.TimeZone
.Windows.Select(t => "Windows:" + x.Name)
.Union(new[] { "Olson:" + x.TimeZone.Name })
.FirstOrDefault()
}).First();
Which should in theory do what I want. Except that for a given point that I tested it with (which I know has a WindowsTimeZone associated with) it returned the OlsonTimeZone instead of the WindowsTimeZone.
If for the same ID i write the following:
return db.LandPoints.Where(x => x.GeoNameID == ID).Take(1).Select(x => new LandPoint
{
TimeZone = x.TimeZone
.Windows.Select(t => "Windows:" + x.Name)
.FirstOrDefault()
}).First();
I get the WindowsTimeZone.
I am sure I could rewrite it using a CASE statement but I felt this was more elegant. Since the way its behaving is somewhat counter intuitive and understanding why it is doing what it does would help me get a better feeling of how linq queries translate to sql I decided to post a question here.
So why is it doing what it does? Is there some addition to the code above that would make it work (maintaining the UNION statement)?
Thanks in advance
John

It is because union doesn't guarantee ordering. You cannot put any special expectations based on order of items without ordering them. But your code should fire NotSupportedException because this is not allowed: db.LandPoints.Select(x => new LandPoint ...
If you have LandPoint entity mapped by entity framework you cannot project to this type in Linq-to-entities.

Related

EF Core 5 check if all ids from filter exists in related entities

I have two models:
public class Employee
{
public int Id { get; set; }
public IList<Skill> { get; set; }
}
public class Skill
{
public int Id { get; set; }
}
And I have filter with list of skill ids, that employee should contain:
public class Filter
{
public IList<int> SkillIds { get; set; }
}
I want to write query to get all employees, that have all skills from filter.
I tried:
query.Where(e => filter.SkillIds.All(id => e.Skills.Any(skill => skill.Id == id)));
And:
query = query.Where(e => e.Skills
.Select(x => x.Id)
.Intersect(filter.SkillIds)
.Count() == filter.SkillIds.Count);
But as a result I get exception says that query could not be translated.
It is going to be a difficult, if not impossible task, to run a query like this on the sql server side.
This is because to make this work on the SQL side, you would be grouping each set of employee skills into a single row which would need to have a new column for every skill listed in the skills table.
SQL server wasn't really made to handle grouping with an unknown set of columns passed into a query. Although this kind of query is technically possible, it's probably not very easy to do through a model binding framework like ef core.
It would be easier to do this on the .net side using something like:
var employees = _context.Employees.Include(x=>x.Skill).ToList();
var filter = someFilter;
var result = employees.Where(emp => filter.All(skillID=> emp.skills.Any(skill=>skill.ID == skillID))).ToList()
This solution works:
foreach (int skillId in filter.SkillIds)
{
query = query.Where(e => e.Skills.Any(skill => skill.Id == skillId));
}
I am not sure about it's perfomance, but works pretty fast with small amount of data.
I've also encountered this issue several times now, this is the query I've come up with that I found works best and does not result in an exception.
query.Where(e => e.Skills.Where(s => filter.SkillIds.Contains(s.Id)).Count() == filter.SkillIds.Count);

Entity Framework include children that grand children are null

Seem like a simple one, but not coming up with the right thing. All that I need to do is get a count of child items that have it's own child null. Here is what I have been working with:
var data = await _context.award.Include(a => a.expenses.Select(p => p.invoice == null)).ToListAsync();
I have also tried other combinations here with no luck. The error I get is
InvalidOperationException: The property expression 'a => {from expense p in [a].expenses select ([p].invoice == null)}' is not valid. The expression should represent a property access: 't => t.MyProperty'.
I change it to match and it just triggers a new error.
I just want to get a list of award with it's list of expenses listed (fine with just the .ID if that influences the solution) where the invoice parent object is not set and is null.
UPDATE
requested models
public class invoice
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey("INV_NUM_ForeignKey")]
public invoice_number fin_invoice_number { get; set; }
}
public class invoice_number
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int number { get; set; }
public invoice invoice { get; set; }
public string display { get { return string.Format("sps-{0}", (new String('0', 6) + number.ToString())).Substring(number.ToString().Count()-7, number.ToString().Count()); } }
}
You have to use .Include together with .ThenInclude. Docs explains it clearly here (Including multiple levels).
var data = await _context.award
.Include(a => a.expenses)
.ThenInclude(e => e.invoice)
.ToListAsync();
Notice: But notice, that ThenInclude has two overloads and chances are big, that Visual Studio will select the wrong one or just display one (wrong one) and give you either errors while typing or do not offer autocompetition for e if e is not a collection. If you ignore the error and type the correct property and close the bracket, the error will disappear.
It seems you know what you are doing but
sometimes it happens and a magic letter solves nightmare problems...
Generally (Which version of EF you use dunno),
as far as i know, like described here
https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
Check your model has related properties
Decide Eagerly or Lazy ...
If thesee are not solution then switch your computer :)..
Then Just in EF configuration check relation definitions
Sorry still can not comment out.I had to write answer...
Try re-writing your code like this
var data = await _context.award.Include(a => a.expenses).Where(p => p.expenses.Any(a => a.invoice == null)).ToListAsync();

Error When Querying For A Substring Using Dynamic Linq

I'm trying to use dynamic linq to obtain a subset of people from a database using Entity
Framework (EF). I'm running into a problem when using the contains operation. Here is the entity
for the People table:
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
Here is a query that works successfully.
var people = personContext
.People
.OrderBy("id asc")
.Skip(0)
.Take(5)
.ToList();
Notice that I'm using dynamic linq in the OrderBy method. However, when I try to apply
filtering, I get an exception.
var people = personContext
.People
.Where("id.Contains(15)")
.OrderBy("id asc")
.Skip(0)
.Take(5)
.ToList();
What I'd like to get back is a subset of people with ids that contain the substring "15", such as:
"015", "115", "151", "152", etc.
When I execute the code, I get the following error.
System.Linq.Dynamic.ParseException was unhandled by user code
Message=No applicable method 'Contains' exists in type 'String'
What is the syntax for determining if the Id field contains the string "15"?
What is the syntax for determining if the Id field contains the string "15"?
Well, definitely not .Where("id.Contains(15)") which is trying to invoke the method Contains with numeric value 15.
According to the documentation, you can use either a string literal:
.Where("id.Contains(\"15\")")
or substitution values:
.Where("id.Contains(#0)", "15")
I feel misconception here... You are not supposed to use LINQ like this.
As a start you need to invoke the overloads that accept lambdas; then you specify the property in the lambda and if its a string you invoke Contains on it. Like so:
var people = personContext
.People
.Where(p => p.Id.Contains("15"))
.OrderByDescending(p => p.Id)
.Skip(0) // You don't need this line.
.Take(5)
.ToList();
The EF itself will do the heavy lifting and translate these pure C# codes into the correct SQL statements.
You can't use Contains in the LINQ query. Instead you can try this
var people = (from p in personContext.Set<People>()
where p.Id.Contains("15")
orderby p.Id
select p).Skip(0).Take(5).ToList();

Entity Framework 6 error - ICollection does not contain a definition for sub-entity

I'm using Entity Framework 6 with MVC and I'm using this query in the controller:
using (var context = new MyDbContext())
{
IQueryable<ContentArticle> query;
query = context.ContentArticle.Where(
c => c.ContentArticleSubdivisions.Subdivisions.Name == subdivision
The compiler is complaining that
'System.Collections.Generic.ICollection<MySite.DAL.Models.ContentArticleSubdivision>' does not contain a definition for 'Subdivisions'
However my content article model includes this property:
public virtual ICollection<ContentArticleSubdivision> ContentArticleSubdivisions { get; set; }
and my content article subdivision model includes this:
public partial class ContentArticleSubdivision
{
...
public virtual ICollection<Subdivision> Subdivisions { get; set; }
}
so what am I doing wrong? I think I need to modify the query so that it's looking up all possible Subdivisions that could be contained in the ContentArticleSubdivisions collection?
You need to use some sort of collection based method to do that. I'm not exactly sure what you are trying to query for here. I'm going to assume you want all articles where a subdivision exists matching that name.
query = context.ContentArticle.Where(
c => c.ContentArticleSubdivisions.Any(cs => cs.Subdivisions.Any(s => s.Name == subdivision))
);
You can also simplify this using the query comprehension syntax like so
query = from ca in context.ContentArticle
from cas in ca.ContentArticleSubdivisions
from s in cas.Subdivisions
where s.Name == subdivision
select ca;

Class with multiple List Properties of same type but with different restrictions

Here's my problem: I have a class that have 2 list properties of the same class type (but with some different restriction as on how to be filled), let's say:
public class Team
{
[Key]
public int IDTeam { get; set; }
public string TeamName { get; set; }
public List<Programmer> Members { get; set; }
public List<Programmer> Leaders { get; set; }
public LoadLists(MyProjectDBContext db)
{
this.Members = db.Programmers.Where(p => p.IDTeam = this.IDTeam
&& (p.Experience == "" || p.Experience == null)).ToList();
this.Leaders = db.Programmers.Where(p => p.IDTeam = this.IDTeam
&& (p.Experience != null && p.Experience != "")).ToList();
}
}
public class Programmer
{
[Key]
public int IDProgrammer { get; set; }
[ForeignKey("Team")]
public int IDTeam { get; set; }
public virtual Team Team { get; set; }
public string Name { get; set; }
public string Experience { get; set; }
}
At some point, I need to take a list of Teams, with it's members and leaders, and for this I would assume something like:
return db.Teams
.Include(m => m.Members.Where(p => p.Experience == "" || p.Experience == null)
.Include(l => l.Leaders.Where(p => p.Experience != null && p.Experience != "")
.OrderBy(t => t.TeamName)
.ToList();
And, of course, in this case I would be assuming it wrong (cause it's not working at all).
Any ideas on how to achieve that?
EDIT: To clarify a bit more, the 2 list properties of the team class should be filled according to:
1 - Members attribute - Should include all related proggramers with no experience (proggramer.Experience == null or "");
2 - Leaders attribute - Should include all related proggramers with any experience (programmer.Experiente != null nor "");
EDIT 2: Here's the MyProjectDbContext declaration:
public class MyProjectDBContext : DbContext
{
public DbSet<Team> Teams { get; set; }
public DbSet<Programmer> Programmers { get; set; }
}
You are talking about EntityFramework (Linq to entities) right? If so, Include() is a Method of Linq To Entities to include a sub-relation in the result set. I think you should place the Where() outside of the Inlcude().
On this topic you'll find some examples on how to use the Include() method.
So I suggest to add the Include()'s first to include the relations "Members" and "Leaders" and then apply your Where-Statement (can be done with one Where()).
return db.Teams
.Include("Team.Members")
.Include("Team.Leaders")
.Where(t => string.IsNullOrWhitespace(t.Members.Experience) ... )
What is unclear to me is your where criteria and your use-case at all as you are talking of getting a list of Teams with Leaders and Members. May above example will return a list of Teams that match the Where() statement. You can look though it and within that loop you can list its members and leaders - if that is the use-case.
An alternative is something like this:
return db.Members
.Where(m => string.IsNullOrWhitespace(m.Experience))
.GroupBy(m => m.Team)
This get you a list of members with no experience grouped by Team. You can loop the groups (Teams) and within on its members. If you like to get each team only once you can add a Distinct(m => m.Team) at the end.
Hope this helps. If you need some more detailed code samples it would help to understand your requirements better. So maybe you can say a few more words on what you expect from the query.
Update:
Just read our edits which sound interesting. I don't think you can do this all in one Linq-To-Entities statement. Personally I would do that on the getters of the properties Members and Leaders which do their own query (as a read-only property). To get performance for huge data amount I would even do it with SQL-views on the DB itself. But this depends a little on the context the "Members" and "Leaders" are used (high frequent etc).
Update 2:
Using a single query to get a table of teams with sublists for members and leaders I would do a query on "Programmers" and group them nested by Team and Experience. The result is then a list of groups (=Teams) with Groups (Experienced/Non-experience) with Programmers in it. The final table then can be build with three nested foreach-Statements. See here for some grouping examples (see the example "GroupBy - Nested").
Whenever you fetch entities, they will be stored in the context -- regardless of the form they are "selected" in. That means you can fetch the teams along with all the necessary related entities into an anonymous type, like this:
var teams =
(from team in db.Teams
select new {
team,
relatedProgrammers = team.Programmers.Where(
[query that gets all leaders OR members])
}).ToList().Select(x => x.team);
It looks like we're throwing away the relatedProgrammers field here, but those Programmer entities are still in memory. So, when you execute this:
foreach (var team in teams) team.LoadLists(db);
...it will populate the lists from the programmers that were already fetched, without querying the database again (assuming db is the same context instance as above).
Note: I haven't tested this myself. It's based on a similar technique shown in this answer.
EDIT - Actually, it looks like your "leaders" and "members" cover all programmers associated with a team, so you should be able to just do Teams.Include(t => t.Programmers) and then LoadLists.

Categories