LINQ to Subsonic Left Outer Join - linq

I have the following query:
var q = from x in content_item.All()
join y in vendor.All() on x.Vendor_ID equals y.Vendor_ID into tmp
from v in tmp.DefaultIfEmpty()
select new { Z=x.Content_Item_Name,W=((v!=null)?v.Vendor_Name:"")};
when I type:
var items = q.ToList();
I got the following exception:
Expression of type 'System.Collections.Generic.IEnumerable`1[Vamp.Models.content_item]' cannot be used for parameter of type 'System.Linq.IQueryable`1[Vamp.Models.content_item]' of method 'System.Linq.IQueryable`1[<>f__AnonymousType0`2[Vamp.Models.content_item,System.Collections.Generic.IEnumerable`1[Vamp.Models.vendor]]] GroupJoin[content_item,vendor,Nullable`1,<>f__AnonymousType0`2](System.Linq.IQueryable`1[Vamp.Models.content_item], System.Collections.Generic.IEnumerable`1[Vamp.Models.vendor], System.Linq.Expressions.Expression`1[System.Func`2[Vamp.Models.content_item,System.Nullable`1[System.UInt32]]], System.Linq.Expressions.Expression`1[System.Func`2[Vamp.Models.vendor,System.Nullable`1[System.UInt32]]], System.Linq.Expressions.Expression`1[System.Func`3[Vamp.Models.content_item,System.Collections.Generic.IEnumerable`1[Vamp.Models.vendor],<>f__AnonymousType0`2[Vamp.Models.content_item,System.Collections.Generic.IEnumerable`1[Vamp.Models.vendor]]]])'
Any idea?
Note: content_item.All() is IQueryable and vendor.All() is IQueryable

Sorry I missed this question back when you asked it...
The left outer join syntax in SubSonic 3 is slightly different. I have a workaround posted as an answer to this question: Subsonic 3.0 Left Join

Hi you need to do something like this, create a getter setter as followed:
public class ReturnProperty
{
public string Z{ get; set; }
public string W{ get; set; }
}
And Change your query like this:
var q = from x in content_item.All()
join y in vendor.All() on x.Vendor_ID equals y.Vendor_ID into tmp
from v in tmp.DefaultIfEmpty()
select new ReturnProperty { Z=x.Content_Item_Name,W=((v!=null)?v.Vendor_Name:"")};
var items = q.ToList();
Hope this helps..

Related

Linq to Objects - Left Outer Join Distinct Object Property Values to an Aggregate Count

Lets say I have a generic list of the the following objects:
public class Supermarket
{
public string Brand { get; set; }
public string Suburb { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
So using a List<Supermarket> which is populated with many of these objects with different values I am trying to:
Select the distinct Suburb properties from a
superset of Supermarket objects contained in a List<Supermarket> (say this superset contains 20
distinct Suburbs).
Join the Distinct List of Suburbs above to another set of aggregated and counted Suburbs obtained by a LINQ query to a different, smaller list of List<Supermarket>
The distinct items in my superset are:
"Blackheath"
"Ramsgate"
"Penrith"
"Vaucluse"
"Newtown"
And the results of my aggregate query are:
"Blackheath", 50
"Ramsgate", 30
"Penrith", 10
I want to join them to get
"Blackheath", 50
"Ramsgate", 30
"Penrith", 10
"Vaucluse", 0
"Newtown", 0
Here is what I have tried so far:
var results = from distinctSuburb in AllSupermarkets.Select(x => x.Suburb).Distinct()
select new
{
Suburb = distinctSuburb,
Count = (from item in SomeSupermarkets
group item by item.Suburb into aggr
select new
{
Suburb = aggr.Key,
Count = aggr.Count()
} into merge
where distinctSuburb == merge.Suburb
select merge.Count).DefaultIfEmpty(0)
} into final
select final;
This is the first time I have had to post on Stack Overflow as its such a great resource, but I can't seem to cobble together a solution for this.
Thanks for your time
EDIT: OK So I solved this a short while after the initial post. The only thing I was missing was chaining a call to .ElementAtOrDefault(0) after the call to .DefaultIfEmpty(0). I also verifed that using .First() instead of .DefaultIfEmpty(0) as Ani pointed out worked, The correct query is as follows:
var results = from distinctSuburb in AllSupermarkets.Select(x => x.Suburb).Distinct()
select new
{
Suburb = distinctSuburb,
Count = (from item in SomeSupermarkets
group item by item.Suburb into aggr
select new
{
Suburb = aggr.Key,
Count = aggr.Count()
} into merge
where distinctSuburb == merge.Suburb
select merge.Count).DefaultIfEmpty(0).ElementAtOrDefault(0)
} into final
select final;
LASTLY: I ran Ani's code snippet and confirmed that it ran successfully, so both approaches work and solve the original question.
I don't really understand the assumed equivalence between State and Suburb (where distinctSuburb == merge.State), but you can fix your query adding a .First() after the DefaultIfEmpty(0) call.
But here's how I would write your query: using a GroupJoin:
var results = from distinctSuburb in AllSupermarkets.Select(x => x.Suburb).Distinct()
join item in SomeSupermarkets
on distinctSuburb equals item.Suburb
into suburbGroup
select new
{
Suburb = distinctSuburb,
Count = suburbGroup.Count()
};

Projections over sub collections - EF4

Lets assume the following model with EF4:
class Order
{
....
public int Id {get;private set;}
//ICollection is the root of all evil here
public ICollection<OrderDetail> Details {get;private set;}
}
I can then project over this structure with Linq:
var IdAndCount = context
.Orders
.Select ( o => new {
Id = o.Id,
Count = o.Details.Where(d => d.Foo > 0).Count()});
So far so good, this will be fully translated to sql.
Now to the problem, what if I want to extract the where clause predicate in this query:
Func<OrderDetail,bool> detailPredicate = d => d.Foo > 0;
var IdAndCount = context
.Orders
.Select ( o => new {
Id = o.Id,
Count = o.Details
.Where(detailPredicate)
.Count()});
This compiles, but fails at runtime because there is no way for EF4 to translate the predicate to SQL since it is a Func and not an Expression.
Changing the predicate to an Expression<Func<OrderDetail,bool>> will not work since ".Where" on the OrderDetails links to the IEnumerable "Where" since the details are ICollection.
So, is it possible to extract parts of a bigger Linq query if the properties beeing traversed are IEnumerable or similair?
Try
Expression<Func<OrderDetail,bool>> detailPredicate = d => d.Foo > 0;

strongly typed view projected linq

I have spent hours upon hours trying to figure out how to shape the data projected from linq to a strongly type view. My problem is I think my problem is I am unsure how to use IEnumberable and IGrouping.
Here is the linq:
var spec = from a in _entities.Approvals
join b in _entities.ApprovalSpecifications on a.HeaderlID equals b.HeaderlID into g
where a.ApprovalID == id
group a by a.HeaderlID into groupedByHeader
select new
{
Key = groupedByHeader.Key,
groupedByHeader
};
Can anyone suggest the method with which I should approach this? I am thinking a class for this would work best, but as I mentioned I'm not sure how to use IGrouping to build a class. Any help is appreciated!
Something like this?
class StronglyTypedGrouping {
public object Key { get; set; } // I can't infer Key type from the snippet.
public IEnumerable<Approval> Approvals { get; set; }
}
var spec = from a in _entities.Approvals
join b in _entities.ApprovalSpecifications on
a.HeaderlID equals b.HeaderlID into g
where a.ApprovalID == id
group a by a.HeaderlID into groupedByHeader
select new StronglyTypedGrouping {
Key = groupedByHeader.Key,
Approvals = groupedByHeader
};

Subsonic 3 Linq Projection Issue

OK I'm banging my head against a wall with this one ;-)
Given tables in my database called Address, Customer and CustomerType, I want to display combined summary information about the customer so I create a query to join these two tables and retrieve a specified result.
var customers = (from c in tblCustomer.All()
join address in tblAddress.All() on c.Address equals address.AddressId
join type in tblCustomerType.All() on c.CustomerType equals type.CustomerTypeId
select new CustomerSummaryView
{
CustomerName = c.CustomerName,
CustomerType = type.Description,
Postcode = address.Postcode
});
return View(customers);
CustomerSummaryView is a simple POCO
public class CustomerSummaryView
{
public string Postcode { get; set; }
public string CustomerType { get; set; }
public string CustomerName { get; set; }
}
Now for some reason, this doesn't work, I get an IEnumerable list of CustomerSummaryView results, each record has a customer name and a postcode but the customer type field is always null.
I've recreated this problem several times with different database tables, and projected classes.
Anyone any ideas?
I can't repro this issue - here's a test I just tried:
[Fact]
public void Joined_Projection_Should_Return_All_Values() {
var qry = (from c in _db.Customers
join order in _db.Orders on c.CustomerID equals order.CustomerID
join details in _db.OrderDetails on order.OrderID equals details.OrderID
join products in _db.Products on details.ProductID equals products.ProductID
select new CustomerSummaryView
{
CustomerID = c.CustomerID,
OrderID = order.OrderID,
ProductName = products.ProductName
});
Assert.True(qry.Count() > 0);
foreach (var view in qry) {
Assert.False(String.IsNullOrEmpty(view.ProductName));
Assert.True(view.OrderID > 0);
Assert.False(String.IsNullOrEmpty(view.CustomerID));
}
}
This passed perfectly. I'm wondering if you're using a reserved word in there?
This post seems to be referring to a similar issue...
http://groups.google.com/group/subsonicproject/browse_thread/thread/2b569539b7f67a34?hl=en&pli=1
Yes, the reason Rob's example works is because his projection's property names match exactly, whereas John's original example has a difference between CustomerType and type.Description.
This shouldn't have been a problem, but it was - the Projection Mapper was looking for properties of the same name and wasn't mapping a value if it didn't find a match. Therefore, your projection objects' properties would be default values for its type if there wasn't an exact name match.
The good news is, I got the latest source today and built a new Subsonic.Core.dll and the behavior is now fixed.
So John's code above should work as expected.
I just downloaded the latest build from 3/21/2010, which is about 2 months after the last poster on this thread, and the problem still exists in the packaged binary. Bummer.
Here what I have to do:
var data =
(from m in Metric.All()
where m.ParentMetricId == parentId
select new
{
m.MetricName,
m.MetricId,
})
.ToList();
var treeData =
from d in data
select new TreeViewItem
{
Text = d.MetricName,
Value = d.MetricId.ToString(),
LoadOnDemand = true,
Enabled = true,
};
return new JsonResult { Data = treeData };
If I try to do the projection directly from the Subsonic query, the Text property ends up with the ID, and the Value property ends up with the Name. Very strange.

linq help - newbie

how come this work
public IQueryable<Category> getCategories(int postId)
{
subnusMVCRepository<Categories> categories = new subnusMVCRepository<Categories>();
subnusMVCRepository<Post_Category_Map> postCategoryMap = new subnusMVCRepository<Post_Category_Map>();
var query = from c in categories.GetAll()
join pcm in postCategoryMap.GetAll() on c.CategoryId equals pcm.CategoryId
where pcm.PostId == 1
select new Category
{
Name = c.Name,
CategoryId = c.CategoryId
};
return query;
}
but this does not
public IQueryable<Category> getCategories(int postId)
{
subnusMVCRepository<Categories> categories = new subnusMVCRepository<Categories>();
subnusMVCRepository<Post_Category_Map> postCategoryMap = new subnusMVCRepository<Post_Category_Map>();
var query = from c in categories.GetAll()
join pcm in postCategoryMap.GetAll() on c.CategoryId equals pcm.CategoryId
where pcm.PostId == postId
select new Category
{
Name = c.Name,
CategoryId = c.CategoryId
};
return query;
}
The issue is most likely in the implementation of the query provider.
pcm.PostId == 1
and
pcm.PostId == postId
actually have a big difference. In the expression tree the first is generated as a ConstantExpression which doesnt need to be evaulated.
With the second, the compiler actually generates an inner class here (this is the _DisplayClassX that you see). This class will have a property (will most likely be the same name as your parameter) and the expression tree will create a MemberAccessExpression which points to the auto-generated DisplayClassX. When you query provider comes accross this you need to Compile() the Lambda expression and evaluate the delegate to get the value to use in your query.
Hope this helps.
cosullivan
The problem is not the linq itself,
you need to be sure that the context or provider object is able to fetch the data.
try testing the
subnusMVCRepository<Categories> categories = new subnusMVCRepository<Categories>();
subnusMVCRepository<Post_Category_Map> postCategoryMap = new subnusMVCRepository<Post_Category_Map>();
objects and see if they are populated or if they behaving as required.
you may want to search the generated code for c__DisplayClass1 and see what you can see there. some times the generated code dose some weird things.
when you step into you code check the locals and the variable values. this may also give you some clues.
Edit : Have you tried to return a List<> collection ? or an Enumerable type?
Edit : What is the real type of the item and query may not be iterable

Resources