Only select certain items from related table using linq - c#

Hi I have a 2 tables BlogPost and BlogComments
I want to get all BlogPosts that have a state of "published".
and get all BlogComments for each post that has a state of "published".
Im using something like this to get the blogposts:
var BlogPosts = (from p in db.BlogPosts where p.State == State.Published select p).ToArray();
but due to the relationship with BlogComments it autiomatically has all the BlogComments (both published and unpublished).
How can I just get the "published" comments for each of the blogposts (i.e the approved ones)
thanks

Try selecting a new BlogPostViewModel, an analog to BlogPost but with an IEnumerable<BlogComment> instead, with just the blog post data and a collection of published comments.
select new BlogPostViewModel {
Title = p.Title,
Body = p.Body,
Comments = p.Comments.Where( c => c.Published );
});
Where BlogPostViewModel is:
public class BlogPostViewModel
{
public string Title { get; set; }
public string Body { get; set; }
public IEnumerable<BlogComment> Comments { get; set; }
}

var BlogPosts = from p in db.BlogPosts where p.State == State.Published select new {
Post = p,
Comments = (from c in db.BlogComments where c.State == State.Published && c.Post = p select c)
};

var publishedComments = db.BlobPosts.Where(p => p.State == State.Published)
.SelectMany(p => p.Comments)
.Where(c => c.State == State.Published);
Sorry for not using the query syntax. Hopefully this will get you on your way though.

Related

Filter on nested property

I have the following models:
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Description { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
I have an IQueryable like:
var results = Blog.Include(x => x.Posts);
Everything works great until I want to filter on a property of the Post class. I need something like this:
var filteredResults = results.Where(x => x.Posts.Where(y => y.Description == "Test"));
This works If I append Any() to the second .Where(). This would not be right though because I only want to return the matching Posts, not all.
Any suggestions on how to approach this?
Entities don't filter like this. A Blog entity will, and should refer to ALL Posts it is associated with. EF can apply global filters to data to accommodate things like soft-delete (IsActive) or tenancy (ClientId) scenarios, but not filtered children like this.
This is a view/consumer concern, not a domain one so you should be looking to separate those concerns using Projection to return the data you want:
string postFilter = "Test";
var filteredResults = context.Blogs
.Where(x => x.Posts.Any(p => p.Description == postFilter))
.Select(x => new BlogViewModel
{
BlogId = x.BlogId,
Title = x.Title,
FilteredPosts = x.Posts.Where(p => p.Description == postFilter)
.Select(p => new PostViewModel
{
PostId = p.PostId,
Description = p.Description,
Text = p.Text,
// ...
{).ToList()
}).ToList();
You could approach this from bottom up.
var blogIds = Posts.Where(x => x.Description == "Test").Select(x => x.BlogId);
var result = Blog.Where(x => blogIds.Contains(x.Id))
Note that you might want to do:
x => x.Description.Contains("Test")
instead of:
x => x.Description == "Test"
in first query
You'll still have to map corresponding posts to each blog though
Update
Steve's answer is correct. I'll just add that it may translate in a lot of nested select queries. You can check the output in SQL Server profiler, or in the output window in Visual Sudio. So here's everything including mapping:
var posts = Posts.Where(x => x.Description == "test").ToList();
var blogIds = posts.Select(x => x.BlogId).ToList();
var blogs = Blog.Where(x => blogIds.Contains(x.Id)).ToList();
foreach(var blog in blogs)
blog.Posts = posts.Where(x => x.BlogId == x.Id).ToList()

LINQ how to Filter the data based on the value of the properties

i have a problem with filtering data in LINQ , here is my Model :
public class CoursePlan
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Semester { get; set; }
public string ModuleCode { get; set; }
public string ModuleName { get; set; }
public string Credits { get; set; }
public string OrderNumber { get; set; }
public string ModuleStatus { get; set; }
}
and here is my data Json
the problem here some modules having same OrderNumber which mean they are optional , student must study one of them and if student already study one of them , i should ignore other modules in same order number.
in other way to describe the question
i want to return a list of CoursePlan and on this list if there is two items having same OrderNumber check the ModuleStatus for each one of them and if any one is Completed remove other modules on that order otherwise return them all .
here is my code
var coursePlanList = await _sqLiteAsyncConnection.Table<CoursePlan>().ToListAsync();
var groupedData = coursePlanList.OrderBy(e => e.Semester)
.GroupBy(e => e.OrderNumber)
.Select(e => new ObservableGroupCollection<string, CoursePlan>(e))
.ToList();
for now im solving this by this algorithm and not sure if it's the best
var coursePlanList = await _sqLiteAsyncConnection.Table<CoursePlan>().ToListAsync();
List<CoursePlan> finalList = new List<CoursePlan>();
var counter = 0;
foreach (var itemPlan in coursePlanList)
{
if (counter > 0 && counter < coursePlanList.Count)
if (itemPlan.OrderNumber == coursePlanList[counter - 1].OrderNumber)
{
if (itemPlan.ModuleStatus == "Completed")
{
finalList.RemoveAll(a => a.OrderNumber == itemPlan.OrderNumber);
finalList.Add(itemPlan);
}
Debug.WriteLine(itemPlan.ModuleName + "With -->" + coursePlanList[counter - 1].ModuleName);
}
else
finalList.Add(itemPlan);
counter++;
}
var groupedData = finalList.OrderBy(e => e.ModuleStatus)
.ThenBy(e => e.Semester)
.GroupBy(e => e.Semester)
.Select(e => e)
.ToList();
CoursePlanViewList.BindingContext = new ObservableCollection<IGrouping<string, CoursePlan>>(groupedData);
Any advise or guidance would be greatly appreciated
Let me rephrase your requirement: you want to show all plans per OrderNumber that meet the condition: none of the plans in their group should be "Completed" or the plans themselves should be "Completed". All this grouped by Semester:
var plansQuery =
from p in _sqLiteAsyncConnection.Table<CoursePlan>()
group p by p.Semester into sem
select new
{
PlansInSemester =
from p in sem
group p by p.OrderNumber into gp
select new
{
PlansInOrderNumber =
gp.Where(p => !gp.Any(p1 => p1.ModuleStatus == "Completed")
|| p.ModuleStatus == "Completed")
}
};
This gives you an IQueryable that produces the course plans you want to select, but grouped in two levels, so the final result is obtained by flattening the query twice:
var coursePlanList = await plansQuery
.SelectMany(x => x.PlansInSemester
.SelectMany(y => y.PlansInOrderNumber)).ToListAsync()

Linq to Entites 6, Many-to-many relationship, Selecting Fields from Both tables into an object

I've spent the afternoon on StackOverflow and Google looking for a way to do a simple SQL query using linq to entities. I am trying to get data from two tables joined by a many-to-many relationship. In SQL I would write the query like this:
SELECT v.[VendorID]
, t.[UnitNumber]
, t.[Name]
, t.[Address]
, t.[CityStateZip]
FROM [Tenant] t
INNER JOIN [TenantVendor] tv ON tv.[TenantID] = t.[TenantID]
INNER JOIN [Vendor] v on v.[VendorID] = tv.[VendorID]
WHERE t.[UnitNumber] LIKE '%100A%'
AND t.[CompanyID] = 17874;
I have an object in a list that I'm selecting the data into that looks like this which gets applied directly to a grid:
public class SearchObject
{
public int IDField { get; set; }
public string UniqueField { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string CityStateZip { get; set; }
}
Here's the kicker, the m2m table is only made up of the primary keys from both tables, so it doesn't have an entity to select. They show up under each others entity as a collection like this (from the tenants entity):
public virtual ICollection<Vendor> Vendors { get; set; }
I would prefer lambda expressions, but query would work fine too. The closest I have got are these two:
SearchData = DBContext.Tenants
.Where(t => t.Company.Name == CompanyName && t.UnitNumber.ToString().Contains(SearchText))
.OrderBy(DynamicSort)
.Skip(StartRow)
.Take(PageSize)
.Select(t => new SearchObject { IDField = t.Vendors, UniqueField = t.UnitNumber.ToString(), Name = t.Name, Address = t.Address, CityStateZip = t.CityStateZip })
.ToList();
That one doesn't work because t.Vendors is a collection, what I want is just the vendorID.
This one works but returns way too many records since it's missing the join between the two tables:
SearchData = (from t in DBContext.Tenants
from v in DBContext.Vendors
where t.Company.Name == CompanyName && t.UnitNumber.ToString().Contains(SearchText)
select new SearchObject { IDField = v.VendorID , UniqueField = t.UnitNumber.ToString(), Name = t.Name, Address = t.Address, CityStateZip = t.CityStateZip })
.ToList();
EDIT / UPDATE
After ElMent provieded me with the correct answer, I figured out how to achieve the same result using C# methods, SelectMany was the key.
SearchData = DBContext.Tenants
.Where(t => t.Company.Name == CompanyName && t.UnitNumber.ToString().Contains(SearchText))
.OrderBy(DynamicSort)
.Skip(StartRow)
.Take(PageSize)
.SelectMany(t=>t.Vendors.Select(v => new SearchObject { IDField = v.VendorID, UniqueField = t.UnitNumber.ToString() + " - " + v.VendorNumber, Name = t.Name, Address = t.Address, CityStateZip = t.CityStateZip }))
.ToList();
What about this. See "from v in t.Vendors" on second line.
SearchData = (from t in DBContext.Tenants
from v in t.Vendors
where t.Company.Name == CompanyName && t.UnitNumber.ToString().Contains(SearchText)
select new SearchObject { IDField = v.VendorID , UniqueField = t.UnitNumber.ToString(), Name = t.Name, Address = t.Address, CityStateZip = t.CityStateZip })
.ToList();
Mor info here:
https://msdn.microsoft.com/en-us/library/bb386932(v=vs.110).aspx

Linq to entities : Many to many in one single select

I have this domain:
public class User
{
public long Id { get; set; }
public string Login { get; set; }
public string Password { get; set; }
}
public class Application
{
public long Id { get; set; }
public string Name { get; set; }
}
public class UserApplications
{
[ForeignKey("User")]
public long UserId { get; set; }
public User User { get; set; }
[ForeignKey("Application")]
public long ApplicationId { get; set; }
public Application Application { get; set; }
public DateTime LastConnection { get; set; }
}
I want to make a select that returns something like that:
List of select new
{
User = user,
Applications = applications // List of all user's applications
}
I try:
from u in Users
join ua in UserApplications on u.Id equals ua.UserId into userApplications
from ua in userApplications.DefaultIfEmpty()
join a in Applications on ua.ApplicationId equals a.Id into applications
select new
{
User = u,
Applications = applications
}
But this repeats the user to each application.
I know that I can do that in two select statements, but I dont want that.
How can I do that?
I do not remember if Entity Frameworks could do groupby based on the entity object itself (and extract it's Id behind the scene and replace things as it fits and the like); but this code works for this case:
var q = from uapp in cntxt.UserApplications
group uapp by uapp.UserId
into g
select new { UserId = g.Key, Applications = g.Select(x => x.Application) };
And if you are willing to have User already extracted:
var q2 = from uapp in cntxt.UserApplications
group uapp by uapp.UserId
into g
let u = Users.First(x => x.Id == g.Key)
select new { User = u, Applications = g.Select(x => x.Application) };
Assuming you are writing a query against an Entity Framework Context - and not just trying to do a Linq to Objects query.
Actually, you just have to group UserApplications entity set by user:
context
.UserApplications
.GroupBy(_ => _.User, _ => _.Application)
.ToList();
because, in fact, IGrouping<User, Application> is what you need (Key is the user, and group items are his applications).
Any other improvements are matter of taste, like projection to anonymous type:
context
.UserApplications
.GroupBy(_ => _.User, _ => _.Application)
.Select(_ => new
{
User = _.Key,
// since IGrouping<User, Application> is IEnumerable<Application>,
// we colud return a grouping directly
Applications = _
})
.ToList();
(another projection option throws away group key in Applications):
context
.UserApplications
.GroupBy(_ => _.User, _ => _.Application)
.Select(_ => new
{
User = _.Key,
Applications = _.Select(app => app)
})
.ToList();
Try this:
var tmp =
from u in Users
join ua in UserApplications on u.Id equals ua.UserId
join a in Applications on ua.ApplicationId equals a.Id
select new
{
User = u,
App = a
};
var res = tmp
.ToArray() // edited
.GroupBy(_ => _.User)
.Select(_ => new
{
User = _.Key,
Applications = _.Select(_ => _.App).ToArray()
});

Unable to cast object of type 'Grouping[System.String,MyProject.ViewModels.MyViewModel+MyClassData]' to type 'MyClassData'

I am having an issue with grouping in my ASP.NET MVC4 (Razor View Engine) VS 2012 project. I am using Entity Framework. My code is below:
Thanks for any help.
I am getting the error
Unable to cast object of type 'Grouping[System.String,MyProject.ViewModels.MyViewModel+MyClassData]' to type 'MyClassData'.
MYCLASS
public class MyClassData
{
public int? Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
}
private readonly List<MyClassData> mData = new List<MyClassData>();
public List<MyClassData> SpecData
{
get { return mData; }
}
IEnumerable<IGrouping<string, MyClassData>>
query = (from t in db.MydbProcedure(temp)
select new MyClassData
{
Id = t.Id,
ParentId = t.ParentId,
Title = t.Title
}).OrderBy(x =>x.Id).GroupBy(y => y.Title);
List<IGrouping<string, MyClassData>> mSpec = query.ToList();
mData .Clear();
mSpec.ForEach(b =>
{
if (b != null)
mData.Add((MyClassData)b);
});
mSpec.ForEach(b =>
{
if (b != null)
mData.AddRange(b);
});
edit:
for more content and quality! YAY!
the problem in your code is: b is no instance of MyClassData ... b is an IGrouping< K,V> and also an IEnumerable< V> ... since V is MyClassData in this case, you can add to mData using the AddRange(IEnumerable< V> obj) Method
in simple words ... you tried to use a collection of MyClassData as a single instance of MyClassData ...
I can't check your code but I think this code will help you:
List<IGrouping<string, MyClassData>> query = new List<MyClassData>()
.GroupBy(it => it.Title)
.ToList();
List<MyClassData> mData = query
.Where(b => b != null)
.SelectMany(it => it)
.ToList();
You can simply add Where and SelectMany clauses to your query in order to get the same result.
I'm not sure if Where clause is really needed in your code.
I think you can write even the following:
List<MyClassData> mData = (from t in db.MydbProcedure(temp)
select new MyClassData
{
Id = t.Id,
ParentId = t.ParentId,
Title = t.Title
})
.OrderBy(it => it.Id)
.GroupBy(it => it.Title)
.Where(b => b != null)
.SelectMany(it => it)
.ToList();

Categories