How to Join 2 Generic IEnumerators - c#

I'm wondering if its possible to join together IEnumerable's.
Basically I have a bunch of users and need to get their content from the database so I can search and page through it.
I'm using LINQ to SQL, my code at the moment it:
public IEnumerable<content> allcontent;
//Get users friends
IEnumerable<relationship> friends = from f in db.relationships
where f.userId == int.Parse(userId)
select f;
IEnumerable<relationship> freindData = friends.ToList();
foreach (relationship r in freindData)
{
IEnumerable<content> content = from c in db.contents
where c.userId == r.userId
orderby c.contentDate descending
select c;
// This is where I need to merge everything together
}
I hope that make some sense!
Matt

If I understand correctly what you are trying to do, why don't you try doing:
var result = from r in db.relationships
from c in db.contents
where r.userId == int.Parse(userId)
where c.userId == r.UserId
orderby c.contentDate descending
select new {
Relationship = r,
Content = c
}
This will give you an IEnumerable<T> where T is an anonymous type that has fields Relationship and Content.

If you know your users will have less than 2100 friends, you could send the keys from the data you already loaded back into the database easily:
List<int> friendIds = friendData
.Select(r => r.UserId)
.Distinct()
.ToList();
List<content> result = db.contents
.Where(c => friendIds.Contains(c.userId))
.ToList();
What happens here is that Linq translates each Id into a parameter and then builds an IN clause to do the filtering. 2100 is the maximum number of parameters that SQL server will accept... if you have more than 2100 friends, you'll have to break the ID list up and combine (Concat) the result lists.
Or, if you want a more literal answer to your question - Concat is a method that combines 2 IEnumerables together by creating a new IEnumerable which returns the items from the first and then the items from the second.
IEnumerable<content> results = Enumerable.Empty<content>();
foreach (relationship r in friendData)
{
IEnumerable<content> content = GetData(r);
results = results.Concat(content);
}

If you're doing an INNER join, look at the .Intersect() extension method.

Which things are you merging?
There are two main options you could use: .SelectMany(...) or .Concat(...)

Related

Linq - how to filter dataset based on data in another table?

I'm looking to filter the data in a dataset much like you'd do a where <value> in (select <value> from other_table where year=2016)
So I have a list of the "values":
var BUs = (from b in dc.BusinessUnits
where b.Year == int.Parse(ddlYears.SelectedValue)
orderby b.BuName
select new { b.BUID }).ToList();
So what I need to do is filter this dataset based on the BUID list returned in the BUs var.
IQueryable<Market> markets = (from p in dc.Markets
orderby p.MarketName
select p);
Help? I'm 100% new to linq so I need a concise solution.
Well if you Market entity has a BUID property and this property it's a primitive type (int, string..) or an enum, you can use Contains method:
var BUs = (from b in dc.BusinessUnits
where b.Year == int.Parse(ddlYears.SelectedValue)
orderby b.BuName
select b.BUID );
IQueryable<Market> markets = (from p in dc.Markets
where BUs.Contains(p.BUID)
orderby p.MarketName
select p);
The standard way of filtering by in memory id list is to use Enumerable.Contains method. But you need first to make sure your list contains ids - they way you wrote it it will contain anonymous type with a property called BUID, by changing the first query like this
int year = int.Parse(ddlYears.SelectedValue);
var BUIDs = (from b in dc.BusinessUnits
where b.Year == year
orderby b.BuName
select b.BUID).ToList();
and then use
var markets = (from p in dc.Markets
where BUIDs.Contains(p.BUID)
orderby p.MarketName
select p);
But note that this will be inefficient. Much better option would be to not use the list of BUIDs for filtering, but combining the 2 queries so the whole thing becomes a single query executed in the database, like this
var markets = (from p in dc.Markets
where dc.BusinessUnits.Any(bu => b.Year == year && b.BUID == p.BUID)
orderby p.MarketName
select p);
This is the exact equivalent of, if using your words, much like you'd do a "where in (select from other_table where year=2016)".

How to assign to a List<string> in LinqToEntities C#

Hoping that I'm just missing something obvious but here's my query
var data = (from project in _db.Projects
where project.Id == id
let primaryCategory = (from c in _db.Categories
where c.CategoryID == project.PrimaryCategory
select c.Name)
let categories = (from c in _db.ProjectCategories
join pc in _db.Projects_ProjectCategories on c.ProjectCategoryID equals pc.ProjectCategoryID
where pc.ProjectID == project.ProjectID
select c.Name)
let owner = (from o in _db.Owners
join po in _db.App_Projects_Owners on o.OwnerID equals po.OwnerID
where po.ProjectID == project.ProjectID
select new OwnerModel
{
Owner = o,
Project = project,
PrimaryCategory = primaryCategory.FirstOrDefault(),
Categories = categories.ToList()
})
select new
{
owner,
project
}).FirstOrDefault();
In there OwnerModel.Categories is a List of strings. I can't use ToList() in the query because it gives a materialization error. I've added a custom setter that takes the IQueryable, but that still makes another round trip to the database for every owner that the query returns.
So how are you supposed to assign basic lists in a subquery?
EDIT AND ANSWER (since Robert McKee lead me to the answer in his comment).
The answer is to use the group by clause like so
var data = (from project in _db.Projects
where project.Id == id
let primaryCategory = (from c in _db.Categories
where c.CategoryID == project.PrimaryCategory
select c.Name)
let categories = (from c in _db.ProjectCategories
join pc in _db.Projects_ProjectCategories on c.ProjectCategoryID equals pc.ProjectCategoryID
where pc.ProjectID == project.ProjectID
group c.Name by pc.ProjectCategoryID into x
select x.ToList())
let owner = (from o in _db.Owners
join po in _db.App_Projects_Owners on o.OwnerID equals po.OwnerID
where po.ProjectID == project.ProjectID
select new OwnerModel
{
Owner = o,
Project = project,
PrimaryCategory = primaryCategory.FirstOrDefault(),
Categories = categories
})
select new
{
owner,
project
}).FirstOrDefault();
Specifically note the bits involving
group c.Name by pc.ProjectCategoryID into x
select x.ToList()
It may seem counter-intuitive that it works like this until you dig into exactly what's going on. The method that I was calling above with categories.ToList() was trying to use the System.Collection.Generics ToList function, and that list didn't have any way to convert the expression to sql. However by using the group by clause I was creating a specialized Enumerable IGrouping and calling the ToList function on that. This function is able to be translated into a sql statement and thus doesn't throw the exception.
Learn something new every day.
Set up your navigation properties, and then your query becomes something like:
var data=db.Projects
.Include(p=>p.PrimaryCategory)
.Include(p=>p.Categories)
.Include(p=>p.Owner) // or .Include(p=>p.Owners) if projects can have multiple owners
.First(p=>p.Id == id);
As for child objects, you are looking for the group by clause or .GroupBy method.

Convert A Union Query To LINQ To Entity Query

Can someone help me with converting this query to a Linq to entities query in the proper way. I am fairly new to Linq and want to write these queries properly. This is a fairly involved one for what im doing with UNION and sub queries in it
SELECT pf.FileID, pf.ServerName, pf.MigrationType
FROM pOrders pf
WHERE pf.FileID IN (select GCMFileID FROM Signals
where SignalFileID = " + FileID + ")
UNION
SELECT pf.FileID, pf.ServerName, pf.MigrationType
FROM pOrders pf
WHERE pf.FileID = " + FileID + "
order by pf.MigrationType desc
I know, I saw comments... but
var signalIds = Signals.Where(s => s.SignalFileId = FILEID).Select(x => x.GCMFileID ).ToArray();
pOrders.Where(pf => signalIds.Contains(pf.FileID))
.Union(
pOrders.Where(pf => pf.FileID == FILEID))
.OrderByDescending(u => u.MigrationType)
.Select(u => new {u.FileID, u.ServerName, u.MigrationType});
var innerquery = from t in db.Signals
where t.SignalFileID == FileID
select new {t.SignalFieldID};
var query = (from p in db.pOrders
where p.FieldID.Contains(innerquery.SignalFieldID)
select new {p.FileID, p.ServerName, p.MigrationType}).Union
(from p in db.pOrders
where p.FieldID ==FieldID
orderby p.MigrationType
select new {p.FileID, p.ServerName, p.MigrationType})
I know this is an old question but I thought I'd add my two cents hoping I can save some time for someone who thinks as I originally did that Union() is the correct method to use.
My first misstep was to create a custom comparer with my entity's logical keys after I hit my first error that the xml column type cannot be used in a distinct. Then, Linq to Entities complained it did not recognize Union(). I notice the accepted answer calls ToArray. This brings the entire results of the first query into memory before doing the Union. The OP wants Linq to Entities so you need to act on an IQueryable. Use Concat. The entire query will run in the database.
var innerquery = (from t in db.Signals
where t.SignalFileID == FileID
select t.SignalFileID);
var query = (from p in db.pOrders
where innerquery.Contains(p.FileID)
select new {p.FileID, p.ServerName, p.MigrationType})
.Concat(from p in db.pOrders
where p.FileID == FileID
select new {p.FileID, p.ServerName, p.MigrationType})
.OrderBy(o => o.MigrationType);

How to select a variable plus some other fields in Linq to object?

Consider the following linq query:
from r in result
join oUS in onOrderUS on r.ItemGUID equals oUS.ItemGUID into jOnOrderUS
from oUS in jOnOrderUS.DefaultIfEmpty()
let OnOrderUS = oUS != null ? oUS.UnitQty : 0
select (new Func<ItemMinMaxView>(() =>
{
r.OnOrderUS = OnOrderUS;
return r;
})).Invoke()
I want to select r from result, but fill a field with the data from oUS. Is there a better way to do this?
I'd rather see your functionality split into its constituent parts. The initial query (join, projection, etc.), the mutation, and then return the specific results you need. Such as
var query = from r in result
join oUS in onOrderUS on r.ItemGUID equals oUS.ItemGUID into jOnOrderUS
from oUS in jOnOrderUS.DefaultIfEmpty()
let OnOrderUS = oUS != null ? oUS.UnitQty : 0
select new { r, OnOrderUS };
foreach (var item in query)
{
item.r.OnOrderUS = item.OnOrderUS;
}
return query.Select(item => item.r);
// assumes this is in a method returning IEnumerable<R>
If you do not want to code the foreach, you could write it like this. I don't like mutations from queries, but this will do what you want
Func<R, int, R> mutateR = (r, onOrderUS) => { r.OnOrderUS = onOrderUS; return r; };
var query = from r in result
join oUS in onOrderUS on r.ItemGUID equals oUS.ItemGUID into jOnOrderUS
from oUS in jOnOrderUS.DefaultIfEmpty()
let OnOrderUS = oUS != null ? oUS.UnitQty : 0
select mutateR(r, OnOrderUS);
I too would like to see the query split into more logical parts to aid readability. But I would then take advantage of the Reactive Extensions (Rx) to cause the side effect you want (ie the value assignment).
First up I would just create the query like Anthony suggested, but I want to check that your query is correct.
If there are more than one onOrderUS records returned then you'll only assign the last one found to the ItemMinMaxView record.
If there will only ever be zero or one record then your query is fine, but it could be simpler.
Either way, try this query instead:
var query =
from r in result
join oUS in onOrderUS on r.ItemGUID equals oUS.ItemGUID into goUSs
let OnOrderUS = goUSs.Sum(x => x.UnitQty)
select new
{
Result = r,
OnOrderUS
};
Then I'd get the final results (with the assignment side effect) like this:
var output = query
.Do(x => x.Result.OnOrderUS = x.OnOrderUS)
.Select(x => x.Result)
.ToArray();
The .Do(...) method is part of Rx and is explicitly there for these kinds of side effects.
You do get a load of good extension methods for IEnumerable<T> in Rx so if you haven't checked it out you should. It's even recently been promoted to a supported Microsoft developer tool.

How do I use linq to do a WHERE against a collection object (using Netflix data source)

I am using LinqPad to learn Linq by querying the NetFlix OData source.
(BTW I know their is a similar question already on SO...didn't help me).
Here is the query I got working which is awesome.
from x in Titles
//where x.Rating=="PG"
where x.Instant.Available==true
where x.AverageRating>=4.0
//where x.Rating.StartsWith("TV")
//where x.Genres.First (g => g.Name.Contains("Family") ) //(from y in Genres where y.Name.Contains("Family") select y)
//where x.Genres.First (g => g.Name=="")
//orderby x.Name
orderby x.AverageRating descending
//select x
//)
select new {x.Name, x.Rating, x.AverageRating, x.ShortSynopsis}
(Pardon all the comments...it is a testament to the fact I am experimenting and that I will change the query for various needs).
There are two thing I cannot figure out.
First. Let's say I only want to return the first 10 results.
Second (and most importantly). I want to filter by a partial string of the genre. Each title contains a Genres collection. I want to show only Genres where the Name contains a certain string (like "Family"). Even better filter using Titles where genre.name.contains "firstFilter" AND "secondFilter".
Basically, I want to filter by genre(s) and I cannot figure out how to do it since Title contains its own Genres collection and I cannot figure out how to return only title that are in one or more genres of the collection.
Thanks for your help!
ps...it seems that Netflix OData source does not support Any operator.
Seth
To return the first 10 results, surround your code above with parentheses and put a .Take(10) on the end
var foo = ( from x in Titles... ).Take(10);
There is no way to do take using query syntax in C# currently.
As for the genre filter, as klabranche points out, oData does not support many of the same Linq constructs you can use locally with a regular IEnumerable or IQueryable.
klabranche's solution doesn't support contains. It does however make 2 round trips to the server to get results. (see my comment on his answer as to why this seems necessary)
Here is an alternative which makes one roundtrip to the server to get data, then it processes that data locally. Because some of the query runs locally, you can use string.Contains, "or" clauses, and other goodness.
The downside of this approach is it retrieves more data over the wire than is needed to answer the query. On the other hand, it's easy to understand and it works.
When I combine "Family" and "Children", it returns 21 results.
var oDataQuery = from x in Titles
where x.AverageRating >= 4
&& x.Instant.Available==true
orderby x.AverageRating descending
select new {x.Name, x.Rating, x.AverageRating, x.ShortSynopsis, x.Genres};
var localQuery = from o in oDataQuery.ToList()
where o.Genres.Any(g => g.Name.Contains("Family"))
&& o.Genres.Any(g => g.Name.Contains("Children"))
select new {o.Name, o.Rating, o.AverageRating, o.ShortSynopsis };
localQuery.Dump();
OData and the Netflix API support the Take() and Contains() methods:
from t in Titles
where t.Name.Contains("Matrix")
select t
(from t in Titles
where t.Name.Contains("Matrix")
select t).Take(10)
To get the first 10:
(from x in Titles
where x.Instant.Available==true
where x.AverageRating>=4.0
orderby x.AverageRating descending
select new {x.Name, x.Rating, x.AverageRating, x.ShortSynopsis}
).Take(10)
To filter by a single genre (Not what you want...):
from g in Genres
from t in g.Titles
where g.Name == "Horror"
where t.Instant.Available==true
where t.AverageRating >=4.0
orderby t.AverageRating descending
select new {t.Name, t.Rating, t.AverageRating, t.ShortSynopsis}
However, you wanted to have multiple genres BUT OData doesn't support Select Many queries which is why contains fails or trying to OR the Genre Name.
Below fails because contains returns many...
var q1 = from g in Genres
from t in g.Titles
where g.Name.Contains("Horror")
where t.Instant.Available==true
where t.AverageRating >=4.0
orderby t.AverageRating descending
select new {t.Name, t.Rating, t.AverageRating, t.ShortSynopsis};
To filter by multiple genres I found you can use a Concat or Union query (in LinqPad be sure to change to C# statements not expression):
var q1 = from g in Genres
from t in g.Titles
where g.Name=="Horror"
where t.Instant.Available==true
where t.AverageRating >=4.0
orderby t.AverageRating descending
select new {t.Name, t.Rating, t.AverageRating, t.ShortSynopsis};
var q2 = from g in Genres
from t in g.Titles
where g.Name=="HBO"
where t.Instant.Available==true
where t.AverageRating >=4.0
orderby t.AverageRating descending
select new {t.Name, t.Rating, t.AverageRating, t.ShortSynopsis};
var concat = q1.ToList().Concat(q2);
//var union = q1.Union(q2);
By unioning the two queries it will remove duplicates but these are what you want If I understand you correctly in that you want movies that are only in both genres?
In that case you will want to use Concat which will return all records.
Now you just need to find records that are in the query more than once and you have your results:
var results = from c in concat
group c by c.Name into grp
where grp.Count() > 1
select grp;

Categories