Runtime joins to query within Entity Framework - c#

I have a database that is mapped using the Entity Framework. Entity Framework is generating the C# code of the database objects in the similar manner as shown below. For simplicity I have created Parent, Child, GrandChild hierarchy but the actual db contains much longer hierarchies and many other fields.
class Parent
{
string name;
int id;
datetime DateOfBirth;
}
class Child
{
string name;
int id;
int ParentId; ( FK reference to Parent Child )
}
class GrandChild
{
string name;
int id;
int ChildId; (FK reference to Child )
}
Now, I am building an api where the filters will be provided at runtime. I mean, some of the queries could be
Give all GrandChild rows for ParentId =1
Give All Grandchild rows for ChildName = "x"
Give all GrandChild rows for Parent with DateOfbirth = "x/y/z"
So, how can I build in C# code, using LINQ or Expression Trees to create predicates and join filters dynamically/runtime.
Following URL:
https://msdn.microsoft.com/library/bb882637.aspx
shows how to create dynamic query, but not how to INNER JOIN multiple such queries. Does anyone know how to do that?
This Stackoverflow answer:
The parameter '***' was not bound in the specified LINQ to Entities query expression
also highlights how to create filters dynamically but not how to join them.
Does anyone know how to create dynamic queries to filter rows and join the queries? Let me know if you need more information. Thanks

Well, I'm going to suppose that is not your real model because you should have properties instead fields and your entities must be public.
If you have represented your db relationships using navigation properties, you could create a extension method like this:
static IQueryable<TEntity> Select<TEntity>(this IQueryable<TEntity> query, List<Expression<Func<TEntity, bool>>> filters = null,
List<Expression<Func<TEntity, object>>> includes = null)
{
if (includes != null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
if (filters != null)
{
query = filters.Aggregate(query, (current, filter) => current.Where(filter)); //at the end this is going to be translated to condition1 && condition2 ...
}
return query;
}
In the first list you pass all the conditions you want to apply to your query and the second helps you to load the related entities that you need in your query:
var conditions = new List<Expression<Func<GrandChild, bool>>>() { (t) => t.Child.Parent.ParentId==1 };
var includes = new List<Expression<Func<GrandChild, object>>>() { (t) => t.Child.Parent };
var query= yourContext.GrandChilds.Select(filters,includes);

1 . Give all GrandChild rows for ParentId =1
var grandChildData=from grand in GrandChild
join ch in Child on grand.ChildId equals ch.Id
where ch.ParentId==1
select grand;

Give All Grandchild rows for ChildName = "x"
var grandChildData=from grand in GrandChild
join ch in Child on grand.ChildId equals ch.Id
where ch.name = "x"
select grand;
Give all GrandChild rows for Parent with DateOfbirth = "x/y/z"
var grandChildData=from grand in GrandChild
join ch in Child on grand.ChildId equals ch.Id
join p in Parent on ch.ParentId equals p.id
where p.DateOfbirth==Convert.ToDateTime("2016/01/01")
select grand;

Related

Property Sum translated to sql on IQueryable with linq to entities

Given two classes generated code first with EF, with a parent-child relation:
class Parent {
//...
public virtual ICollection<Child> Children
}
class Child {
//...
public decimal Amount{ get; set; }
public decimal UnitPrice { get; set; }
}
I would like to create a property Total on Parent, something like
decimal Total => Children.Sum(child => child.UnitPrice * child.Amount)
But if I do this like that, and then do
var list = ctx.Parents
.Where(p => p.Total > 1000)
.Select(p => new {
p.Id,
p.Total,
Count = p.Children.Count });
foreach(var item in list){
Console.WriteLine($"Id: {item.} ...");
}
I got an error that says
An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: The specified type member 'Total' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
While, at my opinion, it should not be that hard for EF to generate an query using SUM(UnitPrice * Amount) as Total I can't get it working.
I have already tried it with a static expression like
public static Expression<Func<Parent, decimal>> GetTotal()
{
return p=> p.Children.Sum(line => line.ItemQty * line.UnitPrice);
}
While it should be acceptable to do this calculation just in code. I want to use this example to learn more about how to use IQueryable.
A Little Succes
Given the following static property on Parent
public static Expression<Func<Parent, decimal?>> Total =>
p=> (from c in p.Childeren
select c.UnitPrice * c.ItemQty).Sum();
And then doing
var list = ctx.Parents.Select(Parent.Total);
I got a list containing all the totals, and I see that EF generated the following query
SELECT
(SELECT
SUM([Filter1].[A1]) AS [A1]
FROM ( SELECT
[Extent2].[UnitPrice] * CAST( [Extent2].[Amount] AS decimal(19,0)) AS [A1]
FROM [dbo].[Child] AS [Extent2]
WHERE [Extent1].[Id] = [Extent2].[ParentId]
) AS [Filter1]) AS [C1]
FROM [dbo].[Parent] AS [Extent1]
So, it EF is indeed capable of translating the Sum() method to SQL. now I only need to use it in the Where().
Calculated properties are only known in the class, not in the database. You'll need to instantiate your objects (into classes) before you can access these calculated properties.
It's not that hard to get this to work:
var list = ctx.Parents
.Where(p => p.Total > 1000)
.ToList() //This instantiates your data into objects
.Select(p => new {
p.Id,
p.Total,
Count = p.Children.Count });
This may require an include statement, depending on whether you've got lazy loading or not:
var list = ctx.Parents
.Include(p => p.Children) //Children will be populated upon instantiation
.Where(p => p.Total > 1000)
.ToList() //This instantiates your data into objects
.Select(p => new {
p.Id,
p.Total,
Count = p.Children.Count });
While, at my opinion, it should not be that hard for EF to generate an query using SUM(UnitPrice * Amount) as Total I can't get it working.
There's more to it than you're mentioning.
The SUM of what? Not of the current result (which is the Parents table), you want a sum of a calculation done on the Children table (grouped by ParentId), which is a completely different dataset than the one you've been working with so far.
This is why EF won't do what you want it to do. It's trying to decide on the SELECT part of the query, but your expectation requires a much more complex query body; one which joins children and parents together in order to perform a calculation on the children and then sum those outcomes into a single value.
If you try writing the SQL query yourself, you'll see that it's much more complicated than just doing SUM(UnitPrice * Amount) as Total.

Query in LINQ with self join

I have a table which contains columns among others like Id as Primary Key and LastMeterReadingId (Foreign Key which references to same table) - something like Parent Meter Reading.
I would like to get all rows which are not already used like Parent. I would like to avoid situation when meter reading is parent for more than one meter reading.
I know how to join to same table, but I have no idea how to choose only those records which aren't parent already. That's how query looks like without condition statement.
return (from m in uow.MeterReadingReadWriteRepository.Query()
join parent in uow.MeterReadingReadWriteRepository.Query() on m.Id equals parent.LastMeterReadingId
select new MeterReadingDto()
{
(...)
}).ToList();
Do you have any idea how to achieve it in efficient way?
Regards.
I would like to get all rows which are not already used like Parent
In other words, you want all rows that have no children. Note that the variable name parent in your query is misleading - when you do a join b on a.Id equals b.ParentId, a is the parent and b is the child.
Anyway, there are at least 3 ways to achieve your goal, IMO being equivalent from nowadays database query optimizers point of view (i.e. should be equally efficient):
(1) Using !Any(...) which is equivalent to SQL NOT EXISTS(...):
from m in uow.MeterReadingReadWriteRepository.Query()
where !uow.MeterReadingReadWriteRepository.Query().Any(child => m.Id == child.LastMeterReadingId)
select ...
(2) Using group join:
from m in uow.MeterReadingReadWriteRepository.Query()
join child in uow.MeterReadingReadWriteRepository.Query()
on m.Id equals child.LastMeterReadingId into children
where !children.Any()
select ...
(3) Using left outer antijoin:
from m in uow.MeterReadingReadWriteRepository.Query()
join child in uow.MeterReadingReadWriteRepository.Query()
on m.Id equals child.LastMeterReadingId into children
from child in children.DefaultIfEmpty()
where child == null
select ...
If this is EF (LINQ to Entities), the first two are translated to one and the same SQL NOT EXISTS based query. While the last is translated to the "traditional" SQL LEFT JOIN ... WHERE right.PK IS NULL based query.
You could just add
where !(from child in uow.MeterReadingReadWriteRepository.Query() where child.Id == m.LastMeterReadingId select child).Any()
Not sure how intelligently this would be optimised though. It would also be better to factor out uow.MeterReadingReadWriteRepository.Query().
Do you not have a Child relationship/collection in your Meter Reading entity from the foreign key constraint? - this would make the query much more straightforward.
var readings = uow.MeterReadingReadWriteRepository.Query();
var parents = readings
.Join(readings, child => child.Id, parent => parent.LastMeterReadingId,
(child, parent) => new {parent.Id})
.Distinct()
.ToDictionary(a => a.Id);
var result = (from m in readings
where !parents.Contains(m.Id)
select new
{
Id = m.Id
}).ToList();
Thanks #Ben Jackson
public class MeterReading : EntityBase
{
public long PropertyId { get; set; }
public long? LastMeterReadingId { get; set; }
public long? PaymentId { get; set; }
public Property Property { get; set; }
public MeterReading LastReading { get; set; }
public Payment Payment { get; set; }
}
That's how most value properties looks like. Maybe should I use T-SQL query with JOIN to CTE which mentioned before condition statement? I'll try your solution ASAP.

When selecting new Type - The entity or complex type cannot be constructed in a LINQ to Entities query

When I call this linq query I get the following error:
The entity or complex type 'DataModel.CustomerContact' cannot be constructed in a LINQ to Entities query.
I don't understand what I am doing wrong here. Normally when I am not joining with any other tables and have no navigation properties I would typically just select cc, but in this case I need to create a new CustomerContact object so I can bind the navigation properties.
I did some research on this and is there really no way to do this? If I use an anonymous type how do I convert to to a CustomerContact since I need to ultimately return a list of CustomerContact to my application? If I simply select cc then cc.CustomerName will not get set. I am really trying to avoid creating Dtos when I should just be able to use the auto-generated EF object classes.
public static IEnumerable<CustomerContacts> GetList(int customerId = null)
{
using (var context = new AppContext())
{
var cList = (from cc in context.CustomerContacts
join c in context.Customers on cc.CustomerId equals c.Id
where (customerId == null || cc.CustomerId == customerId)
select new CustomerContact
{
Id = cc.Id,
FirstName = cc.FirstName,
LastName = cc.LastName,
Email = cc.Email,
// navigation properties
CustomerName = c.Name
}).ToList();
return objList;
}
}
If I simply select cc then cc.CustomerName will not get set
You could do:
from cc in context.CustomerContacts.Include(cc => cc.CustomerName)
...to get the navigation property loaded automatically for you. Search for "EF navigation properties lazy loading".
Note, you need to have 'using ...some namespace I can't remember' to get that syntax to work because it uses an extension method. Otherwise you can just quote the name of the navigation property you want to load, as in:
from cc in context.CustomerContacts.Include("CustomerName")

HQL restriction on child collection

I have a class which has a collection
public class Parent
{
...
ISet<Child> Children;
...
}
Given a list of child names, I'd like to return parents whose Children property contains all items of this list.
I managed to write an HQL query, but it works for a single name, not a whole list :
SELECT p FROM Parent AS p JOIN p.Children AS c WHERE c.Name = 'MyName'
Thanks Mauricio, I managed to solve this problem with the following criteria query
var criteria = session.CreateCriteria<Parent>("p")
.Add(Restrictions.IsNotEmpty("p.Children"));
foreach (var name in namesToMatch)
{
criteria.Add(Subqueries.Exists(DetachedCriteria.For<Parent>("p2")
.SetProjection(Projections.Id())
.CreateAlias("p2.Children", "c")
.Add(Restrictions.Eq("c.Name", name))
.Add(Restrictions.EqProperty("p2.Id", "p.Id"))));
}

Hierarchy Problem -> Replace Recursion with Linq Join?

I have a self referential table, which has ID, ParentID (nullable).
So, the table contains many nodes, each node could be the root in the hierarchy (parent is null), or any level of the hierarchy (parent exists elsewhere in the table).
Given an arbitrary starting node, is there an elegant linq query that will return all children of the hierarchy from that node?
Thanks.
If you want to select all direct children of a node, a simple query like the following should do the job:
from item in table
where item.ID == parentID;
select item
If you want to select all descendants of a node, this is not possible with LINQ, because it requires recursion or a stack which LINQ (and SQL) doesn't provide.
See also:
StackOverflow: LINQ to SQL for self-referencing tables?
CodeProject: T-SQL - How to get all descendants of a given element in a hierarchical table
StackOverflow: Expressing recursion in LINQ
Here is a quick one I just wrote:
class MyTable
{
public int Id { get; set; }
public int? ParentId { get; set; }
public MyTable(int id, int? parentId) { this.Id = id; this.ParentId = parentId; }
}
List<MyTable> allTables = new List<MyTable> {
new MyTable(0, null),
new MyTable(1, 0),
new MyTable(2, 1)
};
Func<int, IEnumerable<MyTable>> f = null;
f = (id) =>
{
IEnumerable<MyTable> table = allTables.Where(t => t.Id == id);
if (allTables
.Where(t => t.ParentId.HasValue && t.ParentId.Value == table
.First().Id).Count() != 0)
return table
.Union(f(
allTables.Where(t => t.ParentId.HasValue && t.ParentId.Value == table
.First().Id).First().Id));
else return table;
};
But I believe it is possible to do using SQL with a Union ALL.
I know this is an old post but you should check out this extension:
http://www.scip.be/index.php?Page=ArticlesNET23
I've been using it and it is working great.
Basically I'm going with something like this as discussed in the SO link you proivded.
public IQueryable GetCategories(Category parent)
{
var cats = (parent.Categories);
foreach (Category c in cats )
{
cats = cats .Concat(GetCategories(c));
}
return a;
}
CTEs are probably the best solution but I'd like to keep things all in the same tier for now.

Categories

Resources