views:

89

answers:

4

I have this SQL query that is just impossible to get going in LINQ.

select * from attribute_value t0
where t0.attribute_value_id in
(
    select t1.attribute_value_id from product_attribute_value t1
    where t1.product_attribute_id in
    (
        select t2.product_attribute_id from product_attribute t2
        where t2.product_id in
        (
            select product_id from product p, manufacturer m
            where p.manufacturer_id = m.manufacturer_id
            and m.name = 'manufacturer name'
        )
        and pa.attribute_id = 1207
    )
)

The where clause also has to be done dynamically later on in the code.

+4  A: 

Try to use Linqer. I remember writing some really convoluted things with it.

On a side note, your query isn't all that complex, you're just going from product to its attribute values. Just make a lot of joins on keys and you're done.

Denis Valeev
I don't know if worked for him, but thanks for sharing, cool software!
Leo Nowaczyk
I'm interested on seeing what that tool gives to the OP in that scenario.
eglasius
+1  A: 

I have successfully implemented 'in' queries by using the Contains() method. For example:

int[] ids = new int[] { 1, 4 };

databasecontext.SomeTable.Where(s => ids.Contains(s.id));

The above will return all records from SomeTable where id is 1 or 4.

I believe you can chain the Contains() methods together. I know it seems backwards, but start with the innermost subselect and work your way out from there.

JungleFreak
See Slaggg's answer above
JungleFreak
+3  A: 

I like to compose Linq queries by writing the discrete components of the query as individual statements. Because each statement is a query rather than a result, Linq will then compose these all together to a single SQL query at run-time.

Writing the query this way, to me, makes it very easy to read, without sacrificing run-time database performance, since Linq makes it into one big query at run-time anyway. It will convert the Contains in the queries below into sub-selects.

Use LinqPad to see the generated SQL - it can be very interesting to see the SQL Linq creates.

Note result itself is a query. To materialize it, do result.ToList();

var productIds = from p in product
                 join m in manufacturer on p.manufacturer_id equals m.manufacturer_id
                 where m.name == 'manufacturer name'
                 select p.product_id;

var productAttributeIds =  from pa in product_attribute
                           where productIds.Contains(pa.product_id)
                           select pa.product_attribute_id;

var attributeValueIds = from pav in product_attribute_value
                        where productAttributeIds.Contains(pav.product_attribute_id)
                        select pav.attribute_value_id;

result = from av in attribute_value
         where attributeValueIds.Contains(av.atttriute_value_id)
         select av;
Slaggg
I don't think that will run as intended, at least not on the last version of linq2sql I tried doing a Contains over linq query. It doesn't allow it, so the only option if doing it that way is to execute the productIds query and send those ids in the .Contains. Because of that you'd end up with a few db rountrips and receiving/sending all that info.
eglasius
linq2sql will translate that pattern into select ... from ... where id in (select ...). The key is to not materialize any of the queries (via ToList()) but leave them as queries. Linq2Sql then merges them all toghether into one large Sql statement.
Slaggg
@Slaggg have you used it? I also expected it to be that way, but in my experience linq2sql doesn't support it, and the only way around it is actually materializing it / which has the issues I mentioned. Maybe support for it was added to .net 4, I haven't tried doing it in this version.
eglasius
I have not replicated the question's schema, but I've used the technique many times. For example this query in a different schemavar categoryIds = from c in Categorieswhere c.Name.StartsWith("A")select c.CategoryId; var productIds = from p in Productswhere categoryIds.Contains(p.CategoryId)select p.ProductId;translates to this sql:DECLARE @p0 VarChar(2) SET @p0 = 'A%'SELECT [t0].[productId]FROM [Products] AS [t0]WHERE EXISTS( SELECT NULL AS [EMPTY] FROM [Categories] AS [t1] WHERE ([t1].[categoryId] = [t0].[categoryId]) AND ([t1].[name] LIKE @p0) )
Slaggg
(sorry about the formatting). Linq can be unpredictable and may sometimes give the dreaded "Queries with local collections are not supported". In that case you can help linq out by changing the syntax. For example this query would get orders containing the product ids from abovevar orderIds = from o in OrderItems where Products.Where(x => categoryIds.Contains(x.CategoryId)).Select(x => x.ProductId).Contains(o.ProductId)select o.OrderId;
Slaggg
which then results in this sql: DECLARE @p0 VarChar(2) SET @p0 = 'A%'-- EndRegionSELECT [t0].[orderId]FROM [OrderItems] AS [t0]WHERE EXISTS( SELECT NULL AS [EMPTY] FROM [Products] AS [t1] WHERE ([t1].[productId] = [t0].[productId]) AND (EXISTS( SELECT NULL AS [EMPTY] FROM [Categories] AS [t2] WHERE ([t2].[categoryId] = [t1].[categoryId]) AND ([t2].[name] LIKE @p0) )) )
Slaggg
+1  A: 

Depends on the model, but you should be able to do it similar to:

var attributes =
    from t0 in db.AttributeValues
    where t0.ProductAttributeValues.Any( t1=> 
        t1.ProductAttribute.AttributeId == 1207 &&
        t1.ProductAttribute.Product.Manufacturers
             .Any(m=> m.name == "manufacturer name")
    )
    select t0;

An alternative, reasonably similar to the query / just translation approach:

var attributes =
    from t0 in db.AttributeValues
    where db.Product_Attribute_Values.Any(t1 => 
        db.Product_Attributes.Any(t2 =>
            t2.product_attribute_id == t1.product_attribute_id &&
            db.Products.Any(p=> 
                 p.product_id == t2.product_id &&
                 db.Manufacturers.Any(m=> 
                      m.manufacturer_id == p.manufacturer_id && 
                      m.name == "manufacturer name"
                 )
            ) &&
            t2.attribute_id = 1207
        ) &&
        t0.attribute_value_id == t1.attribute_value_id
     )
     select t0;
eglasius