I want to combine results from 4 tables and select specific fields using LINQ. Please bear with me since I have not done complex LINQ queries.
Table 1 - Subscriber<
It's not really LINQ that's tripping up here, it's LINQ to Entities. Are you using Entity Framework? Does your model have the relationships defined in it? If you have foreign keys in your database, and build your model in Entity Framework with database first, it will map all the entity relationships for you.
If yes, you can then do something like:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public class Subscriber{
public string Name {get;set;}
public List<Subscription> Subscriptions{get;set;}
}
public class Subscription{
public string Name {get;set;}
}
public class MyViewModelItem{
public string SubscriberName{get;set;}
public string SubscriptionNames {get;set;}
}
public static void Main()
{
Console.WriteLine("Hello World");
// create some dummy data
var data = new List<Subscriber>{
new Subscriber{
Name = "Arnold",
Subscriptions = new List<Subscription>(){
new Subscription{
Name = "Subscription A"
},
new Subscription{
Name = "Subscription B"
},
new Subscription{
Name = "Subscription C"
}
}
},
new Subscriber{
Name = "Betty",
Subscriptions = new List<Subscription>()
},
new Subscriber{
Name = "Christopher",
Subscriptions = new List<Subscription>(){
new Subscription{
Name = "Subscription A"
}
}
}
};
// here's the query and it becomes much simpler
var myViewModel = data
.Select(i=> new MyViewModelItem{
SubscriberName = i.Name,
SubscriptionNames = string.Join(", ", i.Subscriptions.Select(j=>j.Name))
})
.ToList();
// this shows the output
foreach(var item in myViewModel){
Console.WriteLine(string.Format("subscriber: {0}, subscriptions: {1}",item.SubscriberName,item.SubscriptionNames));
}
}
}
Output:
Hello World subscriber: Arnold, subscriptions: Subscription A, Subscription B, Subscription C subscriber: Betty, subscriptions: subscriber: Christopher, subscriptions: Subscription A
The results is not including subscribers without subscriptions.
When writing queries, always first try to determine the root entity. You're interested in subscriptions, so it seems obvious to take Subscription
as root entity. But in fact you want to see whether or not subscribers have subscriptions, and if so, which. Subscriber is the root entity, so start the query there.
Figured out how to concatenate the string
Sure, db.Subscriptions.ToList()
does allow you to do anything that LINQ-to-objects has in store, but it's very inefficient. First, you pull all Subscription
data into memory. Then, in var model = (from s in query ...
you join with DbSet
s that each pull all their data into memory. (Because query
is IEnumerable
and, hence, can't be combined with IQueryable
s into one expression and then translated into one SQL statement).
The strategy for using non-supported methods in LINQ-to-Entities queries is: query the exact amount of data --no more, no less-- then continue in memory.
Both points amount to this query:
var query = from s in db.Subcribers // root entity
select new
{
Subscriber_ID = s.Subscriber_ID,
FirstName = s.SubscriberFirstName,
LastName = s.SubscriberLastName,
Address1 = s.SubscriberAddress1,
Address2 = s.SubscriberAddress2,
Email = s.SubscriberEmail,
Organization = s.SubscriberOrganizationName,
Phone = s.SubscriberPhone,
City = s.SubscriberCity,
Zip = s.SubscriberZipcode,
// Navigation properties here
State = (s.SubscriberState_ID == 54) ? s.SubscriberState : s.State.StateName,
StateAbbv = (s.SubscriberState_ID == 54) ? s.SubscriberState : s.State.StateAbbreviation,
Country = s.Country.CountryName,
// Empty list when no subscriptions
Pubs = s.Subscriptions.Select(x => x.SubscriptionPublication).Distinct()
};
var result = query.AsEnumerable() // continue in memory
Select(s => new SubscriberGridViewModel
{
Subscriber_ID = s.Subscriber_ID,
FirstName = s.FirstName,
LastName = s.LastName,
Address1 = s.Address1,
Address2 = s.Address2,
Email = s.Email,
Organization = s.Organization,
Phone = s.Phone,
City = s.City,
State = s.State,
StateAbbv = s.StateAbbv,
Country = s.Country,
Zip = s.Zip
Pub = string.Join(", ", s.Pubs)
}));
Of course, if you're querying almost all fields from Subscriber
this can be a bit less verbose: select new { Subscriber = s, Pubs = .. }
etc. But I usually experience that the performance gain of narrowing down the SQL result set is greatly underestimated as compared to shortening it by filtering.