Expressions: Breaking up a complex linq query - c#

So I have a linq query which has several joins and where clauses, and in the projection it accesses one of the joins. So, I want to break this query up to make it reusable and have tried all sorts of things but can't get it to work. I've put the main query with the joins into another class and returned it as an IQueryable... but then because you have to perform a select at the end, the Join objects are no longer available to the projection later on.
I put the where clauses as expressions into another class, but again have problems when I want to access the joins and place a 'where' onto one of the joins... since they are no longer available to the where clauses.
So, my question is... is it even possible to do something like this? Can you rewrite the main part of the query (select * from x join y join z) as expressions and place into another file, such that all parts of that query can be accessible later on.
I've heard that linq is just a series of expressions... but maybe someone with more knowledge can confirm how one could do something like this?
An example would be:
var data2 = QueryExtensions.GetEmployeeMeetings(_context, id)
.Where(Expressions.Meetings())
Then in the QueryExtensions class you would have
public static IQueryable<table1> GetEmployeeMeetings(EmployeeEntities _context, long id)
{
return (from t in _context.table1
join ed in _context.table2 on t.ID equals ed.ID
join edt in _context.table3 on ed.ID equals edt.ID
select t);
}
Finally, in the Expressions class
public static Expression<Func<table3, bool>> Meetings()
{
return edt => edt.ID == 5;
}
So, unfortunately this doesn't work, because edt is no longer available to the Where lambda that has been created (because in the main query you selected 't' which was just the table1. Obviously I don't want to return ALL the data just to be able to do something like this... I would just like to know if it is possible to break up queries like this, and then put them back together?

Ok I think I just managed to figure this out by creating a join class of those three tables (as below) and then returning that instead of just 't'... seemed to work ok
public class Jointclass
{
public table1 c1 { get; set; }
public table2 c2 { get; set; }
public table3 c3 { get; set; }
}

Related

Refactoring LINQ statement using Entity Framework to a method

I have these Linq queries that I use multiple times in my code. I want to make a method of them and return their results.
var myevent = (from v in myEntities.Events
where _EventID == v.EventID
select v).SingleOrDefault();
var comments = (from c in myEntities.Fora
orderby c.DateCreated descending
where c.EventID == _EventID
select c).ToList();
Your question isn't very clear, but it sounds like you just want two methods to return the results of the linq query.
public Events GetEvent(int id)
{
return (from v in myEntities.Events
where _EventID == id
select v).SingleOrDefault();
}
public List<Fora> GetComments(int id)
{
return (from c in myEntities.Fora
orderby c.DateCreated descending
where c.EventID == id
select c).ToList();
}
Your question has too many unknowns to answer concretely, and this is something the SO is particular about.
I'm going to assume your entity is DB-backed.
You need to be aware of the transaction scope. You could use a separate class with a static "myEntities" and implement as Anonymous above. However if different threads are calling in you may end up with a nested transaction. You also don't want to create a new connection every time you call it.
I would implement this as part of a broader data access layer and pass your connection to it as part of the constructor. If you choose to use a static method then you should pass the connection as one of the params.
Or you could have an abstract class with this logic, and have child classes implement it.

Build Where Clause (not sure PredicateBuilder will work for me)

I am trying to query a database using LINQ. I am joining TableA with TableB with TableC.
I have zero to many 'keywords' (don't know how many at design time) that I would like to look for within (LIKE '%%') several fields that are spread across the three tables.
Assuming three (3) keywords are entered into my search box:
In T-SQL I would have this -
SELECT tbl0.FieldA, tbl0.FieldB, tbl1.FieldC, tbl1.FieldD, tbl2.FieldE, tbl2.FieldF
FROM tbl0
JOIN tbl1 ON tbl0.KeyField = tbl1.KeyField
JOIN tbl2 ON tbl1.KeyField = tbl2.KeyField
WHERE (tbl0.FieldA LIKE '%{keyword1}%' OR tbl1.FieldC LIKE '%{keyword1}%' OR tbl2.FieldE LIKE '%{keyword1}%' OR tbl0.FieldA LIKE '%{keyword2}%' OR tbl1.FieldC LIKE '%{keyword2}%' OR tbl2.FieldE LIKE '%{keyword2}%' OR tbl0.FieldA LIKE '%{keyword3}%' OR tbl1.FieldC LIKE '%{keyword3}%' OR tbl2.FieldE LIKE '%{keyword3}%')
Question is -- How do I 'dynamically' build this WHERE clause in LINQ?
NOTE #1 -- I do not (for reasons outside the scope of this question) want to create a VIEW across the three tables
NOTE #2 -- Because I am joining in this way (and I am still new to LINQ) I don't see how I can use the PredicateBuilder because I am not sure what TYPE (T) to pass into it?
NOTE #3 -- If it matters ... I am ultimately planning to return a strongly typed list of (custom) objects to be displayed in a GridView.
EDIT - 8/17/2012 - 5:15 PM EDT
The comment below is correct.
"The code the OP is looking for is where any one of the fields contains any one of the keywords."
Thanks everyone!
Here's a solution not using the PredicateBuilder. Just get all the items containing the first keyword and merge it with all the items containing the second keyword and so on. Not knowing anything about the context of the problem I can't tell if this will be efficient or not.
var query = from t0 in db.Table0
join t1 in db.Table1 on t0.KeyField equals t1.KeyField
join t2 in db.Table2 on t1.KeyField equals t2.KeyField
select new
{
t0.FieldA, t0.FieldB,
t1.FieldC, t1.FieldD,
t2.FieldE, t2.FieldF
};
string keyword = keywordsList[0];
var result = query.Where(x => x.FieldA.Contains(keyword) ||
x.FieldC.Contains(keyword) ||
x.FieldE.Contains(keyword));
for (int i = 1; i < keywordsList.Length; i++)
{
string tempkey = keywordsList[i];
result = result.Union(query.Where(x => x.FieldA.Contains(tempkey) ||
x.FieldC.Contains(tempkey) ||
x.FieldE.Contains(tempkey)));
}
result = result.Distinct();

IQueryable Linq query with multiple joins at inner level

EDIT: forgot to say I'm using Fluent NHibernate, even though the tag could hint about it anyway.
I have these entity classes:
class OuterLevel
{
ICollection<MidLevel> mid_items;
... other properties
}
class MidLevel
{
OuterLevel parent;
Inner1 inner1;
Inner2 inner2;
... other properties
}
class Inner1
{
int id;
string description;
}
class Inner2
{
int id;
string description;
}
I need to build a Linq query that returns a list of OuterLevel objects with all children populated properly.
Supposing all mappings are correct and working, the hard part I'm finding here is that the resulting query should be something like
SELECT * FROM OuterLevelTable OLT INNER JOIN MidLevelTable MLT ON (MLT.parentID = OLT.ID) INNER JOIN
Inner1Table ON (MLT.Inner1ID = Inner1Table.ID) INNER JOIN
Inner2Table ON (MLT.Inner2ID = Inner2Table.ID)
WHERE (Inner1Table.someproperty1 = somevalue1) AND (Inner2Table.someproperty2 = somevalue2)
The main problem is that two joins start from MidLevel object downward the hierarchy, so I cannot figure out which Fetch and FetchMany combination can be used without having the resulting query join two times the MidLevelTable, such as the following does:
return All().FetchMany(x => x.mid_items).ThenFetch(x => x.inner1).FetchMany(x => x.mid_items).ThenFetch(x => x.inner2);
I would like to return a IQueryable that can be further filtered, so I would prefer avoiding Query and QueryOver.
Thanks in advance,
Mario
what you want is not possible because when you filter on the joined tables the resulting records are not enough to populate the collections anyway. You better construct the query in one place to further tune it or set the collection batch size to get down SELECT N+1.

Counting in a Linq Query

I have a fairly complicated join query that I use with my database. Upon running it I end up with results that contain an baseID and a bunch of other fields. I then want to take this baseID and determine how many times it occurs in a table like this:
TableToBeCounted (One to Many)
{
baseID,
childID
}
How do I perform a linq query that still uses the query I already have and then JOINs the count() with the baseID?
Something like this in untested linq code:
from k in db.Kingdom
join p in db.Phylum on k.KingdomID equals p.KingdomID
where p.PhylumID == "Something"
join c in db.Class on p.PhylumID equals c.PhylumID
select new {c.ClassID, c.Name};
I then want to take that code and count how many orders are nested within each class. I then want to append a column using linq so that my final select looks like this:
select new {c.ClassID, c.Name, o.Count()}//Or something like that.
The entire example is based upon the Biological Classification system.
Assume for the example that I have multiple tables:
Kingdom
|--Phylum
|--Class
|--Order
Each Phylum has a Phylum ID and a Kingdom ID. Meaning that all phylum are a subset of a kingdom. All Orders are subsets of a Class ID. I want to count how many Orders below to each class.
select new {c.ClassID, c.Name, (from o in orders where o.classId == c.ClassId select o).Count()}
Is this possible for you? Best I can do without knowing more of the arch.
If the relationships are as you describe:
var foo = db.Class.Where(c=>c.Phylum.PhylumID == "something")
.Select(x=> new { ClassID = x.ClassID,
ClassName = x.Name,
NumOrders= x.Order.Count})
.ToList();
Side question: why are you joining those entities? Shouldn't they naturally be FK'd, thereby not requiring an explicit join?

Return IQueryable containing 2 Subclasses

Can you return IQueryable which is composed of two or more different subclasses ? Here's my attempt to show what I mean. It throws the error:
System.NotSupportedException: Types in
Union or Concat have members assigned
in different order..
var a = from oi in db.OrderItems
where oi.OrderID == ID
&& oi.ListingID != null
select new OrderItemA {
// etc
} as OrderItem;
var b = from oi in db.OrderItems
where oi.OrderID == ID
&& oi.AdID != null
select new OrderItemB {
//etc
} as OrderItem;
return a.Concat<OrderItem>(b);
Try doing the concat on IEnumerable instead of IQueryable:
return a.AsEnumerable().Concat(b.AsEnumerable());
If you need an IQueryable result you could do this:
return a.AsEnumerable().Concat(b.AsEnumerable()).AsQueryable();
Doing this will force the concat to happen in-memory instead of in SQL, and any additional operations will also happen in-memory (LINQ To Objects).
However, unlike the .ToList() example, the execution should still be deferred (making your data lazy loaded).
My guess is that this is because you are using LINQ in an LINQ-to-SQL context.
So using Concat means that LINQ2SQL will need to join both query into a SQL UNION query which might be where the System.NotSupportedException originated from.
Can you try this:
return a.ToList().Concat<OrderItem>(b.ToList());
And see if it make any difference?
What the above does is that it executes the query twice and then concatenate them in-memory instead of hot-off-SQL as to avoid the query translation problem.
It might not be the ideal solution, but if this work, my assumption is probably correct, that it's a query translation problem:
More information about Union and Concat translation to SQL:
http://blog.benhall.me.uk/2007/08/linq-to-sql-difference-between-concat.html
http://msdn.microsoft.com/en-us/library/bb399342.aspx
http://msdn.microsoft.com/en-us/library/bb386979.aspx
Hope this helps.
Interestingly after reading your post and a bit of testing, I realized that what your actually doing does seem to work just fine for me given that the projection part you show as ellipsis in both of your queries match. You see, LINQ to SQL appears to construct the underlying projection for the SQL select command based off of the property assignment statements as opposed to the actual type being materialized so as long as both sides have the same number, type, and order (not sure about this) of member assignments the UNION query should be valid.
My solution that I've been working with is to create a property on my DataContext class which acts much like a SQL View in that it allows me to write a query (in my case a Union between two different tables) and then use that query as if it is itself like a table when composing read-only select statements.
public partial class MyDataContext
{
public IQueryable<MyView> MyView
{
get
{
var query1 =
from a in TableA
let default_ColumnFromB = (string)null
select new MyView()
{
ColumnFromA = a.ColumnFromA,
ColumnFromB = default_ColumnFromB,
ColumnSharedByAAndB = a.ColumnSharedByAAndB,
};
var query2 =
from a in TableB
let default_ColumnFromA = (decimal?)null
select new MyView()
{
ColumnFromA = default_ColumnFromA,
ColumnFromB = b.ColumnFromB,
ColumnSharedByAAndB = b.ColumnSharedByAAndB,
};
return query1.Union(query2);
}
}
}
public class MyView
{
public decimal? ColumnFromA { get; set; }
public string ColumnFromB { get; set; }
public int ColumnSharedByAAndB { get; set; }
}
Notice two key things:
First of all the projection formed by the queries which make up both halves of the Union have the same number, type, and order of columns. Now LINQ may require the order to be the same (not sure about this) but it is definitely true that SQL does for a UNION and we can be sure that LINQ will require at least the same type and number of columns and these "columns" are known by the member assignments and not from the properties of the type you are instantiating in your projection.
Secondly LINQ currently doesn't allow for multiple constants to be used within a projections for queries which formulate a Concat or Union and from my understanding this is mainly because these two separate queries are separately optimized before the Union operation is processed. Normally LINQ to SQL is smart enough to realize that if you have a constant value which is only being used in the projection, then why send it to SQL just to have it come right back the way it was instead of tacking it on as a post process after the raw data comes back from SQL Server. Unfortunately the problem here is that this is a case of LINQ to SQL being to smart for it's own good, as it optimizes each individual query too early in the process. The way I've found to work around this is to use the let keyword to form a range variable for each value in the projection which will be materialized by getting it's value from a constant. Somehow this tricks LINQ to SQL into carrying these constants through to the actual SQL command which keeps all expected columns in the resulting UNION. More on this technique can be found here.
Using this techinque I at least have something reusable so that no matter how complex or ugly the actual Union can get, especially with the range variables, that in your end queries you can write queries to these pseudo views such as MyView and deal with the complexity underneath.
Can you do the projection after the concat?
// construct the query
var a = from oi in db.OrderItems
where oi.OrderID == ID
&& oi.ListingID != null
select new {
type = "A"
item = oi
}
var b = from oi in db.OrderItems
where oi.OrderID == ID
&& oi.AdID != null
select new {
type = "B"
item = oi
}
var temp = a.Concat<OrderItem>(b);
// create concrete types after concatenation
// to avoid inheritance issue
var result = from oi in temp
select (oi.type == "A"
? (new OrderItemA {
// OrderItemA projection
} as OrderItem)
: (new OrderItemB {
// OrderItemB projection
} as OrderItem)
);
return result
Not sure if the ternary operator works in LINQ2SQL in the above scenario but that might help avoid the inheritance issue.

Categories