views:

1163

answers:

5

I felt like the following should be possible I'm just not sure what approach to take.

What I'd like to do is use the include method to shape my results, ie define how far along the object graph to traverse. but... I'd like that traversal to be conditional.

something like...

dealerships
    .include( d => d.parts.where(p => p.price < 100.00))
    .include( d => d.parts.suppliers.where(s => s.country == "brazil"));

I understand that this is not valid linq, in fact, that it is horribly wrong, but essentially I'm looking for some way to build an expression tree that will return shaped results, equivalent to...

select *
from dealerships as d
outer join parts as p on d.dealerid = p.dealerid
    and p.price < 100.00
outer join suppliers as s on p.partid = s.partid
    and s.country = 'brazil'

with an emphasis on the join conditions.

I feel like this would be fairly straight forward with esql but my preference would be to build expression trees on the fly.

as always, grateful for any advice or guidance

A: 

Am I missing something, or aren't you just looking for the Any keyword?

var query = dealerships.Where(d => d.parts.Any(p => p.price < 100.00) || 
                              d.parts.suppliers.Any(s => s.country == "brazil"));
lc
i don't think so, as i understand it, this would return dealerships where there were parts and suppliers that met the conditions, rather than return all dealership, all of the parts that are under 100 and all the suppliers in brazil. I realise that this should be an outer join. i think what i'm trying to do is prune the tree of results rather than filter top level nodes based on values down the tree.
tim
+1  A: 

Are you sure this is what you want? The only reason I ask is, once you add the filter on Parts off of Dealerships, your results are no longer Dealerships. You're dealing in special objects that are, for the most part, very close to Dealerships (with the same properties), but the meaning of the "Parts" property is different. Instead of being a relationship between Dealerships and Parts, it's a filtered relationship.

Or to put it another way, if I pull a dealership out of your results and passed to a method I wrote, and then in my method I call:

var count = dealership.Parts.Count();

I'm expecting to get the parts, not the filtered parts from Brazil where the price is less than $100.

If you don't use the dealership object to pass the filtered data, it becomes very easy. It becomes as simple as:

    var query = from d in dealerships
               select new { DealershipName = d.Name, 
CheapBrazilProducts = dealership.Parts.Where(d => d.parts.Any(p => p.price < 100.00) || d.parts.suppliers.Any(s => s.country == "brazil")) };

If I just had to get the filtered sets like you asked, I'd probably use the technique I mentioned above, and then use a tool like Automapper to copy the filtered results from my anonymous class to the real class. It's not incredibly elegant, but it should work.

I hope that helps! It was an interesting problem.

Darren
Thanks DarrenI agree in so far as object identity! I'm also concerned that, this is exactly why this may not be supported by the EF. What I'm really trying to do here is parse a query string such as http://carpartsdb.com/dealerships/parts(price < 100)/suppliers(s.country == brazil)into a query expression and check it at the EF, chuck in a custom xml serialisation strategy and presto you have a RestQL Engine (restful query language).I'm not too tied to EF but want to be able have model abstraction and expression trees and EF is and don't want to deviate too far from the MS stack!
tim
sorry darren, see this text in further answer where it'll be more readable!
tim
A: 

Thanks Darren!

I agree in so far as object identity! I'm also concerned that, this is exactly why this may not be supported by the EF.

What I'm really trying to do here is parse a query string such as

carpartsdb.com/dealerships/parts(price < 100)/suppliers(s.country == brazil)

into a query expression and chuck it at the EF, with a custom xml serialisation strategy... presto you have a RestQL Engine (restful query language).

In this scenario the caller should be well aware of what they are getting back (ie an incomplete representation) and it seems to me that using conditional joins and stripping out into hierarchical structures will be quicker (and easier for the caller) than to use sub-queries for each child which blows out as the breadth of the tree/graph expands!

I'm not too tied to EF but want to be able have model abstraction and expression trees and don't want to deviate too far from the MS stack

Would appreciate your further comments

tim
+3  A: 

This should do the trick:

using (TestEntities db = new TestEntities())
{
    var query = from d in db.Dealership
                select new
                {
                    Dealer = d,
                    Parts = d.Part.Where
                    (
                        p => p.Price < 100.0 
                             && p.Supplier.Country == "Brazil"
                    ),
                    Suppliers = d.Part.Select(p => p.Supplier)
                };

    var dealers = query.ToArray().Select(o => o.Dealer);
    foreach (var dealer in dealers)
    {
        Console.WriteLine(dealer.Name);
        foreach (var part in dealer.Part)
        {
            Console.WriteLine("  " + part.PartId + ", " + part.Price);
            Console.WriteLine
                (
                "  " 
                + part.Supplier.Name 
                + ", " 
                + part.Supplier.Country
                );
        }
    }
}

This code will give you a list of Dealerships each containing a filtered list of parts. Each part references a Supplier. The interesting part is that you have to create the anonymous types in the select in the way shown. Otherwise the Part property of the Dealership objects will be empty.

Also, you have to execute the SQL statement before selecting the dealers from the query. Otherwise the Part property of the dealers will again be empty. That is why I put the ToArray() call in the following line:

var dealers = query.ToArray().Select(o => o.Dealer);

But I agree with Darren that this may not be what the users of your library are expecting.

Jakob Christensen
A: 

Yes that's what I wanted to do I think the next realease of Data Services will have the possiblity to do just that LINQ to REST queries that would be great in the mean time I just switched to load the inverse and Include the related entity that will be loaded multiple times but in theory it just have to load once in the first Include like in this code

return this.Context.SearchHistories.Include("Handle")
    .Where(sh => sh.SearchTerm.Contains(searchTerm) && sh.Timestamp > minDate && sh.Timestamp < maxDate);

before I tried to load for any Handle the searchHistories that matched the logic but don't know how using the Include logic you posted so in the mean time I think a reverse lookup would be a not so dirty solution