Join data from IQueryable and IList<KeyValuePair> - c#

I'm trying to join a row of parent data to a related collection which has been squeezed into a single piece of data.
I have an IQueryable of Orders:
IQueryable<Order> orderList = context.Set<Order>
.Where("OrderDate >= #0", startDate)
.Where("OrderDate <= #0", endDate)
and a related IList<KeyValuePair<int, string>> where each KVP contains the OrderID and a concatenated string of the product names from each Order Line. I want to join the Value from the correct KeyValuePair to the Order info based on the OrderID Key.
To illustrate, the desired output would look something like:
OrderNum OrderDate Customer State OrderTotal Products_Ordered
12345 12/12/2012 J.Bloggs WA $25.50 Bolts, Hammer, Suregrip Clamp
I am trying a linq join that looks like this:
IQueryable result = from o in orders
join line in orderLines on o.OrderID equals line.Key
select new
{
o.OrderNum
o.OrderDate,
o.Customer.CustomerFullName,
o.DeliverAddress.State,
o.TotalPrice,
line.Value
}
The method performing the join seems to work, but when I access the returned IQueryable, I get a NotSupportedException: Unable to create a constant value of type 'System.Collections.Generic.KeyValuePair`2'. Only primitive types or enumeration types are supported in this context.
What am I doing wrong?

Your IQueryable is actually a specialized entity framework implementation that, when iterated, attempts to construct an SQL query by examining an expression tree, execute this query, and return an enumerable over the results. This is fragile, and your projections and queries can't be arbitrarily complex, or the expression -> SQL converter has no idea what to do with it.
Fixing this by materializing your IQueryable first is fine, but you don't even really need to do that. Why have a list of what is essentially tuples, when what you want is a dictionary that maps the order ID to a bunch of data?
IDictionary<int, string> orderLines = new Dictionary<int, string>();
// Add a dummy item
orderLines[1234] = "Hello, this, is, a, test";
// Get your combined view
// Assuming you have an order with 1234 as the ID, this should work
var result = from o in orders
select new
{
o.OrderNum
o.OrderDate,
o.Customer.CustomerFullName,
o.DeliverAddress.State,
o.TotalPrice,
Products = orderLines[o.OrderID]
}

Entity Framework has issues with some types, because it tries to push them to the backing database. The quick and easy solution is to pull the data down before performing the join.
var orderList = context.Set<Order>
.Where("OrderDate >= #0", startDate)
.Where("OrderDate <= #0", endDate)
.ToList(); // ------> Relevant line <------
var result = from o in orderList
join line in orderLines on o.OrderID equals line.Key
select new
{
o.OrderNum
o.OrderDate,
o.Customer.CustomerFullName,
o.DeliverAddress.State,
o.TotalPrice,
line.Value
};
It's just a change in where the joining happens, RDBMS- or client-side.
Performance won't be an issue, assuming you don't have a ton of data and all rows will be matched. Of course, you will have to pull down data that doesn't get joined, so if only a few of orderList will be in result, that might be worth a second thought, design-wise. The only alternative would be in pushing the KeyValuePair items to the server first, which probably isn't what you're looking for.

Joining a query result (IQueryable) with a normal enumerable stored in memory (IEnumerable<...>) doesn't make much sense. Think about it, the query doesn't get processed until it's materialized, injecting your own data in queries is done in the query text itself -- that's not what you really want, now is it?
I think what you expect from this is best achieved by first materializing the IQueryable into an IEnumerable<>, then doing a plain LINQ join on two IEnumerable<>s, which is trivial.
It's not like you'd be using the IEnumerable<> to filter the result set on the server side, you're not really losing any performance here.
Edit: Note that if you are using the IEnumerable<> to filter the results on the server side, you can do that! EF (I assume that's what you're using) has very strong special cases for things like IQueriable<>.Any<>() with an IEnumerable<>.Contains<>() inside it -- it inserts the literal values in the query text. It's just the actual join that doesn't make much sense in this context.

Related

LINQ - Left Join converting RIGHT results to List

I'm struggling to find the correct combination of LINQ Methods to perform a multi-table left join with a one to many mapping that makes a list along with the grouped results.
Current Status
I have a Plan table, joined with other tables to get the columns I need to get my list of plans.
var plans = await (
from ubp in db.ViewUserBusinessPlan
join bp in db.ViewBusinessPlan on ubp.BusinessPlanId equals bp.BusinessPlanId
where bp.BusinessId == businessId
select new
{
ubp.UserBusinessPlanId,
ubp.BusinessPlanId,
bp.Name,
bp.PlanGroup,
bp.BusinessId,
ubp.BusinessLocationId,
ubp.StripeSubscriptionId,
ubp.UserId,
ubp.BusinessPlanPriceCents
}
Problem:
Now I need to ultimately get the applicable Tax Rates that are associated with those Plans. This is stored in 2 tables.
UserBusinessPlanTaxRates - (a mapping table) that contains UserBusinessPlanId and TaxRateId.
TaxRates - TaxRate Information with TaxRateId as PK
Some plans have tax rates, some do not, so need a LEFT JOIN type scenario. Also, some plans can have multiple tax rates so I need a list of TaxRates. I've tried various Group methods, subqueries, and left joins. But nothing seems to put them all together.
I want to get all plans, with a list of TaxRates.
You can add a sub-query in the select to do the necessary join:
TaxRates = db.UserBusinessPlanTaxRates.Where(ubptr => ubptr.UserBusinessPlanId == ubp.UserBusinessPlanId)
.GroupJoin(db.TaxRates, ubptr => ubptr.TaxRateId, tr => tr.TaxRateId, (ubptr, trj) => trj)
.SelectMany(trj => trj)
.ToList()
Whether it will translate properly (or optimally) to SQL depends on what LINQ to database you are using.

Using two different data context in a LINQ JOIN Query

I went through the many questions that were asked regarding this and tried to find solution but no luck. So here is my situation:
private IQueryable<tblB> MT;
var IDs = (from z in db1.tblA
where z.TA == User.Identity.Name
select z).ToArray();
MT = from s in db2.tblB
join a in IDs on s.BP equals a.BP
select new tblB() { LastName = s.LastName});
return View(MT.ToPagedList(pageNumber, pageSize));
I'm getting exception at the return statement - $exception {"Unable to create a constant value of type 'tblA'. Only primitive types or enumeration types are supported in this context."} System.NotSupportedException
When I debug IDs array, I see it has data from tblA but 2nd query with join doesn't seem to work. What mistake am I making. Help!!!
You need to use Contains in order to generate and IN sql clause:
First, change the first query to return the primitive data you need:
var IDs = (from z in db1.tblA
where z.TA == User.Identity.Name
select z.BP).ToArray();
Then use that in-memory list in the second query:
MT = from s in db2.tblB
where IDs.Contains(s.BP)
select new tblB() { LastName = s.LastName});
By the way, this is not a 2 contexts operations. You're loading data from the first context into memory (notice the .ToArray()) and then using these in-memory data to query the second context.
So you want as a result all elements of tblB that have a property BP equal to at least one of the BP properties of the elements in IDs.
The problem is that after ToList() IDs is in local memory, while your db1.tblB is on the database. You have to bring both sequences to the same platform, either both to your database, or both to local memory. Whatever is more efficient depends on the actual sizes of the sequences and of the results
Use Contains if you want to perform the query on database side. The complete list of IDs will be transferred to the database, where the query will be executed and the results will be transferred to local memory.
Use this method if IDs is fairly short and your result is not almost the complete tblA
var result = db2.tblB
.Where(elementOfTableB => IDs.Contains(elementOftableB);
No need to create a new tblB object, apparently you want the complete tblB object.
Use AsEnumerable if you expect that there are a lot of IDs in comparison to the number of element in tblB. Transferring the IDs to the database would take considerably more time than transferring the complete tblB to local memory.
db2.TblB // take all tblBelements
.AsEnumerable() // bring to local memory
.join(IDs, // join with list IDs
tblBElement => tblBElement.BP // take from each tblBElement the BP
idElement => idElement.BP // take from each ID the BP
(tblBElement, idElement) => // when they match take the two records
// and get what you want.
tblBElement); // in this case the complete tblBElement

Making a UNION query more efficient in LINQ

I am currently working on a project leveraging EF and I am wondering if there is a more efficient or cleaner way to handle what I have below.
In SQL Server I could get the data I want by doing something like this:
SELECT tbl2.* FROM
dbo.Table1 tbl
INNER JOIN dbo.Table2 tbl2 ON tbl.Column = tbls2.Colunm
WHERE tbl.Column2 IS NULL
UNION
SELECT * FROM
dbo.Table2
WHERE Column2 = value
Very straight forward. However in LINQ I have something that looks like this:
var results1 = Repository.Select<Table>()
.Include(t => t.Table2)
.Where(t => t.Column == null);
var table2Results = results1.Select(t => t.Table2);
var results2 = Repository.Select<Table2>().Where(t => t.Column2 == "VALUE");
table2Results = table2Results.Concat(results2);
return results2.ToList();
First and foremost the return type of the method that contains this code is of type IEnumerable< Table2 > so first I get back all of the Table2 associations where a column in Table1 is null. I then have to select out my Table2 records so that I have a variable that is of type IEnumerable. The rest of the code is fairly straightforward in what it does.
This seems awfully chatty to me and, I think, there is a better way to do what I am trying to achieve. The produced SQL isn't terrible (I've omitted the column list for readability)
SELECT
[UnionAll1].*
FROM (SELECT
[Extent2].*
FROM [dbo].[Table1] AS [Extent1]
INNER JOIN [dbo].[Table2] AS [Extent2] ON [Extent1].[Column] = [Extent2].[Column]
WHERE [Extent1].[Column2] IS NULL
UNION ALL
SELECT
[Extent3].*
FROM [dbo].[Table2] AS [Extent3]
WHERE VALUE = [Extent3].[Column]) AS [UnionAll1]
So is there a cleaner / more efficient way to do what I have described? Thanks!
Well, one problem is that your results may not return the same data as your original SQL query. Union will select distinct values, Union All will select all values. First, I think your code could be made a lot clearer like so:
// Notice the lack of "Include". "Include" only states what should be returned
// *with* the original type, and is not necessary if you only need to select the
// individual property.
var firstResults = Repository.Select<Table>()
.Where(t => t.Column == null)
.Select(t => t.Table2);
var secondResults = Repository.Select<Table2>()
.Where(t => t.Column2 == "Value");
return firstResults.Union(secondResults);
If you know that it's impossible to have duplicates in this query, use Concat instead on the last line (which will produce the UNION ALL that you see in your current code) for reasons described in more detail here. If you want something similar to the original query, continue to use Union like in the example above.
It's important to remember that LINQ-to-Entities is not always going to be able to produce the SQL that you desire, since it has to handle so many cases in a generic fashion. The benefit of using EF is that it makes your code a lot more expressive, clearer, strongly typed, etc. so you should favor readability first. Then, if you actually see a performance problem when profiling, then you might want to consider alternate ways to query for the data. If you profile the two queries first, then you might not even care about the answer to this question.

How to query entities from a collection without using foreach

I've been struggling and trying to get my mind wrapped around these concepts. Any help would be greatly appreciated. Given a collection of Orders in one table what is the best way to iterate over it, and perform queries against other tables.
This loop does what I want, but I've been told and shown how this causes performance problems. On other posts I've read where it is best to query the dbase outside of loops.
foreach (var item in myOrders)
{
var lookUpValues=(from f in ORDERS
where f.ORDERS ==item.ORDER_ID
&& f.ORDERS_SUFFIX==item.LINE
select f.Something);
}
Rather than pulling down the entire Order table prior to iterating over I've tried something like this, but as you see it won't return the accurate rows.
var orderIds=myOrders.Select (x =>x.ORDER_ID).Distinct().ToList();
var lineIds=myOrders.Select (x =>x.LINE).Distinct().ToList();
var query=(from f in ORDERS
where orderIds.Contains(f.ORDERS)
&& lineIds.Contains(f.ORDERS_SUFFIX)
select f).ToList();
The below would do what I think is needed. It would return the correct Orders that are required to iterate over. However, it throws an error, which I believe is because you can't query memory objects and entities together.
var ordersAndLine=(from f in myOrders select new{sId=f.ORDER_ID,line=f.LINE}).ToList();
var query=(from f in ORDERS
from s in ordersAndLine
where s.sId==f.ORDERS
&& s.line==f.ORDERS_SUFFIX
select f).ToList();
NotSupportedException: Unable to create a constant value of type 'Anonymous type'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
I really hope my question makes sense. I apologize in advance if I didn't explain my situation correctly. What I've been doing is pulling down much of the table to memory to iterate over, but I was concerned that this would cause performance issues and wanted to see if there is a better way to filter the result query.
If you combine your first attempt and your last attempt you'd have it. The last attempt would be fine if you weren't creating an anonymous type.
var ordersAndLine=(from f in myOrders select f).ToList();
var query=(from f in ORDERS
from s in ordersAndLine
where s.ORDER_ID==f.ORDERS
&& s.LINE==f.ORDERS_SUFFIX
select f).ToList();
Have a look at these:
http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/thread/095745fe-dcf0-4142-b684-b7e4a1ab59f0/
Linq to Entities - SQL "IN" clause
Try this:
var orders = myOrders.SelectMany(item => (from f in ORDERS
where f.ORDERS ==item.ORDER_ID
&& f.ORDERS_SUFFIX==item.LINE
select f.Something));

Linq to SQL problem

I have a local collection of record Id's (integers).
I need to retrieve records that have every one of their child records' ids in that local collection.
Here is my query:
public List<int> OwnerIds { get; private set; }
...
filteredPatches = from p in filteredPatches
where OwnerIds.All(o => p.PatchesOwners.Select(x => x.OwnerId).Contains(o))
select p;
I am getting this error:
Local sequence cannot be used in Linq to SQL implementation of query operators except the Contains() operator.
I understand that .All() isn't supported by Linq to SQL, but is there a way to do what I am trying to do?
Customers where
OrderIds in the child collection are a subset of the IDs in the in-memory collection.
from c in myDC.Customer
where c.Orders.All(o => myList.Contains(o.ID))
select c;
Customers where
OrderIds in the in-memory collection are a subset of the IDs in the child collection.
from c in myDC.Customers
where (from o in c.Orders
where myList.Contains(o.ID)
group o.ID by o.ID).Distinct().Count() == myList.Count()
select c;
Customers where
OrderIds in the in-memory collection are set-equal to the IDs in the child collection.
from c in myDC.Customers
let Ids = c.Orders.Select(o => o.ID).Distinct()
where Ids.Count() == myList.Count()
&& Ids.All(id => myList.Contains(id))
select c;
All of these generated sql for me.
PS - these presume the IDs are already distinct in myList. If they aren't yet, use:
myList = myList.Distinct().ToList();
PSS - good for lists up to ~2000 items. Higher than that will get translated to sql, and then sql server will barf at the number of parameters.
I don't know of a way to do it with Linq to SQL. The problem is that you need to get your list over to the server so that it can query against it. (your list is in memory on your machine, SQL Server needs to do the filtering on the server)
With straight SQL, you could use a regular SELECT statement with the "in()" operator to do that. (don't go over 1,000 items in the "in")
You could insert all of the ID's into a temp table in SQL, and then join to the table (you could use LINQ with this solution, but it requires 2 steps - the insert (assuming you have a "sets" table), and then the joined query (and then a cleanup query to remove your set).
You could LINQ query without the filter condition and then filter on your in-memory set (not recommended if the unfiltered result set could be large).
what the compiler says is...
OwnerIds.Contains(someVariable)
is supported and it will be translated as:
WHERE someVariable IN (OwnerId1, OwnerId2, OwnerIdN)
now, we don't have all the informations of you query but if you can reformulate what you're trying to do to use Contains, you'll be ok.
Could you do a join OwnerIds?
Error said "Local sequence (means OwnerIds) cannot be used in Linq to SQL implementation of query operators except the Contains() operator."
So you can do:
1) load ALL filteredPatches rows from SQL
var loadedData = filteredPatches.Select(i => i).ToList();
2) filter data as simple local sequence
var result = loadedData.Where(i => i.PatchesOwners.All(o => OwnerIds.Contains(o.ID)));

Categories