Which LINQ expression do I need for this, without looping? - c#

I have an MSSQL database with LINQ to SQL.
I have three tables.
Requests -> id, string name
Results -> id, requestID, int jumps
Places -> id, resultID, int location
Then, using an input string, I need to get an ICollectable or array or something of Place which meets the following:
Each Request that has name=input, take its ID.[you can assume only one has]
Each Result that has requestID=ID[from above] - take its id.
Each Place that has resultID='id[from above]' - append to array for further processing.
I made it by looping on all Results and then executing another LINQ statement, but its extremely slow [about 500ms for a single request!]. Can I make it any faster?
Thank you!
Edit: Whoops, I also need it grouped by result. aka a List of List of Places, while each inner list contains one column from Result.

You can perform table joins in Linq2Sql using the join keyword:
var places = from request in Requests
join result in Results on request.Id equals result.requestID
join place in Places on result.Id equals place.ResultId
where request.name = input
select place;

Somthing like
Requests.Where(r => r.name == input).Results.Places.Select();
If this is too slow then I expect you need some indexes on your database.
If you don't have the relationships in your model then you need to establish some foreign key constraints on your tables an rebuild your model.

Related

LINQ Grouping a List of Objects into Anonymous Type

I am having difficulty trying to use LINQ to query a sql database in such a way to group all objects (b) in one table associated with an object (a) in another table into an anonymous type with both (a) and a list of (b)s. Essentially, I have a database with a table of offers, and another table with histories of actions taken related to those offers. What I'd like to be able to do is group them in such a way that I have a list of an anonymous type that contains every offer, and a list of every action taken on that offer, so the signature would be:
List<'a>
where 'a is new { Offer offer, List<OfferHistories> offerHistories}
Here is what I tried initially, which obviously will not work
var query = (from offer in context.Offers
join offerHistory in context.OffersHistories on offer.TransactionId equals offerHistory.TransactionId
group offerHistory by offerHistory.TransactionId into offerHistories
select { offer, offerHistories.ToList() }).ToList();
Normally I wouldn't come to SE with this little information but I have tried many different ways and am at a loss for how to proceed.
Please try to avoid .ToList() calls, only do if really necessary. I have an important question: Do you really need all columns of OffersHistories? Because it is very expensive grouping a full object, try only grouping the necessary columns instead. If you really need all offerHistories for one offer then I'm suggesting to write a sub select (this is also cost more performance):
var query = (from offer in context.Offers
select new { offer, offerHistories = (from offerHistory in context.OffersHistories
where offerHistory.TransactionId == offer.TransactionId
select offerHistory) });
P.s.: it's a good idea to create indexes for foreign key columns, columns that are used in where and group by statements, those are going to make the query faster,

Selecting Consecutive String Entries with LINQ to Entities

At first you might think this is duplicate of this question but hopefully you will see it is not.
I also want to select groups of rows that are consecutive but consider that this time the entries are telephone numbers, therefore, stored as string.
I have been trying somethink like:
var numbers = await (from a in context.Telephones
from b in context.Telephones
Convert.ToInt32(a.Number) < Convert.ToInt32(b.Number) &&
Convert.ToInt32(b.Number) < (Convert.ToInt32(a.Number) + numberQuantity)
group b by new { a.Number }
into myGroup
where myGroup.Count() + 1 == numberQuantity
select myGroup.Key.Number).ToListAsync();
But this fails with:
LINQ to Entities does not recognize the method 'Int32 ToInt32(System.String)' method, and this method cannot be translated into a store expression.
I understand that LINQ to Entities does not support Convert.ToInt32 but I am running out of ideas here to make it work.
So if my database has:
2063717608
2063717609
2063717610
2063717611
2063717613
2063717614
How can I select consecutive rows based on the string values? And when querying for 3 consecutive numbers get results like:
From 2063717608 to 2063717610
From 2063717609 to 2063717611
1- If you are aware of performance side effect of calling AsEnumerable() cast your query and do conversion in memory on the retrieved entities.
2- If you don't want solution #1, you have to look for a way to solve the conversion problem:
2-1- Either change the column type in the database to int
2-2- Or select one of the solution previously proposed by other developers such as:
Problem with converting int to string in Linq to entities

Reusing LINQ query results in another LINQ query without re-querying the database

I have a situation where my application constructs a dynamic LINQ query using PredicateBuilder based on user-specified filter criteria (aside: check out this link for the best EF PredicateBuilder implementation). The problem is that this query usually takes a long time to run and I need the results of this query to perform other queries (i.e., joining the results with other tables). If I were writing T-SQL, I'd put the results of the first query into a temporary table or a table variable and then write my other queries around that. I thought of getting a list of IDs (e.g., List<Int32> query1IDs) from the first query and then doing something like this:
var query2 = DbContext.TableName.Where(x => query1IDs.Contains(x.ID))
This will work in theory; however, the number of IDs in query1IDs can be in the hundreds or thousands (and the LINQ expression x => query1IDs.Contains(x.ID) gets translated into a T-SQL "IN" statement, which is bad for obvious reasons) and the number of rows in TableName is in the millions. Does anyone have any suggestions as to the best way to deal with this kind of situation?
Edit 1: Additional clarification as to what I'm doing.
Okay, I'm constructing my first query (query1) which just contains the IDs that I'm interested in. Basically, I'm going to use query1 to "filter" other tables. Note: I am not using a ToList() at the end of the LINQ statement---the query is not executed at this time and no results are sent to the client:
var query1 = DbContext.TableName1.Where(ComplexFilterLogic).Select(x => x.ID)
Then I take query1 and use it to filter another table (TableName2). I now put ToList() at the end of this statement because I want to execute it and bring the results to the client:
var query2 = (from a in DbContext.TableName2 join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();
Then I take query1 and re-use it to filter yet another table (TableName3), execute it and bring the results to the client:
var query3 = (from a in DbContext.TableName3 join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();
I can keep doing this for as many queries as I like:
var queryN = (from a in DbContext.TableNameN join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();
The Problem: query1 is takes a long time to execute. When I execute query2, query3...queryN, query1 is being executed (N-1) times...this is not a very efficient way of doing things (especially since query1 isn't changing). As I said before, if I were writing T-SQL, I would put the result of query1 into a temporary table and then use that table in the subsequent queries.
Edit 2:
I'm going to give the credit for answering this question to Albin Sunnanbo for his comment:
When I had similar problems with a heavy query that I wanted to reuse in several other queries I always went back to the solution of creating a join in each query and put more effort in optimizing the query execution (mostly by tweaking my indexes).
I think that's really the best that one can do with Entity Framework. In the end, if the performance gets really bad, I'll probably go with John Wooley's suggestion:
This may be a situation where dropping to native ADO against a stored proc returning multiple results and using an internal temp table might be your best option for this operation. Use EF for the other 90% of your app.
Thanks to everyone who commented on this post...I appreciate everyone's input!
If the size of TableName is not too big to load the whole table you use
var tableNameById = DbContext.TableName.ToDictionary(x => x.ID);
to fetch the whole table and automatically put it in a local Dictionary with ID as key.
Another way is to just "force" the LINQ evaluation with .ToList(), in the case fetch the whole table and do the Where part locally with Linq2Objects.
var query1Lookup = new Hashset<int>(query1IDs);
var query2 = DbContext.TableName.ToList().Where(x => query1IDs.Contains(x.ID));
Edit:
Storing a list of ID:s from one query in a list and use that list as filter in another query can usually be rewritten as a join.
When I had similar problems with a heavy query that I wanted to reuse in several other queries I always went back to the solution of creating a join in each query and put more effort in optimizing the query execution (mostly by tweaking my indexes).
Since you are running a subsequent query off the results, take your first query and use it as a View on your SQL Server, add the view to your context, and build your LINQ queries against the view.
Have you considered composing your query as per this article (using the decorator design pattern):
Composed LINQ Queries using the Decorator Pattern
The premise is that, instead of enumerating your first (very constly) query, you basically use the decorator pattern to produce a chain of IQueryable that is a result of query 1 and query N. This way you always execute the filtered form of the query.
Hope this might help

How can I make this SelectMany use a Join?

Given that I have three tables (Customer, Orders, and OrderLines) in a Linq To Sql model where
Customer -- One to Many -> Orders -- One to Many -> OrderLines
When I use
var customer = Customers.First();
var manyWay = from o in customer.CustomerOrders
from l in o.OrderLines
select l;
I see one query getting the customer, that makes sense. Then I see a query for the customer's orders and then a single query for each order getting the order lines, rather than joining the two. Total of n + 1 queries (not counting getting customer)
But if I use
var tableWay = from o in Orders
from l in OrderLines
where o.Customer == customer
&& l.Order == o
select l;
Then instead of seeing a single query for each order getting the order lines, I see a single query joining the two tables. Total of 1 query (not counting getting customer)
I would prefer to use the first Linq query as it seems more readable to me, but why isn't L2S joining the tables as I would expect in the first query? Using LINQPad I see that the second query is being compiled into a SelectMany, though I see no alteration to the first query, not sure if that's a indicator to some problem in my query.
I think the key here is
customer.CustomerOrders
Thats an EntitySet, not an IQueryable, so your first query doesn't translate directly into a SQL query. Instead, it is interpreted as many queries, one for each Order.
That's my guess, anyway.
How about this:
Customers.First().CustomerOrders.SelectMany(item => item.OrderLines)
I am not 100% sure. But my guess is because you are traversing down the relationship that is how the query is built up, compared to the second solution where you are actually joining two sets by a value.
So after Francisco's answer and experimenting with LINQPad I have come up with a decent workaround.
var lines = from c in Customers
where c == customer
from o in c.CustomerOrders
from l in o.OrderLines
select l;
This forces the EntitySet into an Expression which the provider then turns into the appropriate query. The first two lines are the key, by querying the IQueryable and then putting the EntitySet in the SelectMany it becomes an expression. This works for the other operators as well, Where, Select, etc.
Try this query:
IQueryable<OrderLine> query =
from c in myDataContext.customers.Take(1)
from o in c.CustomerOrders
from l in o.OrderLines
select l;
You can go to the CustomerOrders property definition and see how the property acts when it used with an actual instance. When the property is used in a query expression, the behavior is up to the query provider - the property code is usually not run in that case.
See also this answer, which demonstrates a method that behaves differently in a query expression, than if it is actually called.

NHibernate Criteria with multiple results

Using HQL, you can build a query like the following which will return an array of objects for each row selected by simply using a comma delimited list in the select clause:
select mother, offspr, mate.Name
from Eg.DomesticCat as mother
inner join mother.Mate as mate
left outer join mother.Kittens as offspr
How would you do this with an ICriteria? Specifically, I am trying to build a paged query which also returns the total row count in the same query.
Ayende shows how you can do this with HQL (http://ayende.com/Blog/archive/2007/04/27/Paged-data--Count-with-NHibernate-The-really-easy-way.aspx). I need to replicate this using an ICriteria.
I would presume that the answer lies in prefetching the associations that you want to. That way you can fetch the needed part of your object graph in a single shot. You would do this in criteria queries like so.
ICriteria query = session.CreateCriteria(typeof (Cat))
.SetFetchMode("Mate", FetchMode.Join)
.SetFetchMode("Kittens", FetchMode.Join);
IList<Cat> results = query.List<Cat>();
This will give you back a list of cats with both the Mate and the Kittens prepopulated. You can then navigate to these properties without incurring an N+1 penalty. If you need a more flattened result I'd do it using linq, perhaps like this.
var results = query.List<Cat>()
.Select((c) => new {mother = c, mate = c.Mate, offspr = c.Kittens});
This will give you back a flattened list of anonymous types with the given properties. This will work if all you need is prefetching the object graph. However if you need to prefetch things such as counts or sums then you will need to examine the Projections and Alias parts of Criteria queries.
One more thing. If you are trying to exactly duplicate your above query, you can do so like this.
ICriteria query = session.CreateCriteria(typeof (Cat), "mother")
.CreateAlias("Mate", "mate", JoinType.InnerJoin)
.CreateAlias("Kittens", "offspr", JoinType.LeftOuterJoin)
.SetResultTransformer(Transformers.AliasToEntityMap);
This will return you basically the same as your hql query, however instead of using an indexed list it will use a dictionary that maps the alias to the entity.
You can convert your criteria to a count criteria by this way
DetachedCriteria countCriteria = CriteriaTransformer.TransformToRowCount(criteria); // criteria = your criteria
rowCount = countCriteria.GetExecutableCriteria(session).UniqueResult<int>();
Ayende also added Multi Criteria support: http://ayende.com/Blog/archive/2007/05/20/NHibernate-Multi-Criteria.aspx. That's in the 2.0 release, apparently.
You can also do paging by manipulating the resultset on the Criteria:
ICriteria query = session.CreateCriteria(typeof (Cat), "mother")
query.SetFirstResult(x); // x = page start
query.SetMaxResults(y); // y = page size
List results = query.List();

Categories

Resources