The right way to apply GroupBy extension method with aggregate function - entity-framework

I have this simple model.
public class Room
{
public Guid Id { get; set; }
public Guid? postSubjectId { get; set; }
[ForeignKey("postSubjectId")]
public PostSubject postSubject { get; set; }
public string MemberId { get; set; }
[ForeignKey("MemberId")]
public AppUser Member { get; set; }
}
Basically I need to get Grouped postSubjectId along with MemberId.Count() , I know it's easy .. but it never comes with the expected result.
I made this simple GroupBy query
var mmbrs = _context.Rooms
.Select(g => new { id = g.postSubjectId, mmbrscount = g.MemberId })
.AsEnumerable()
.GroupBy(g => new { id = g.id , mmbrscount = g.mmbrscount.Count() }).ToList();
but it gives me unexpected result
However I did the same using ordinary sql query
select [postSubjectId] as postId, count([MemberId]) as mmbrsCount from [dbo].[Rooms] group by [postSubjectId]
and It gives me result as expected
I need to apply that expected result using LINQ GruoupBy extention method

The grouping key new { id = g.postSubjectId, mmbrscount = g.MemberId }) is like typing group by [postSubjectId], count([MemberId]) in SQL.
The correct statement is:
_context.Rooms
.GroupBy(r => r.postSubjectId)
.Select(g => new
{
id = g.Key,
mmbrscount = g.Count()
})

So every Room has exactly one property PostSubjectId, and one string property MemberId.
I need to get Grouped postSubjectId along with MemberId.Count()
Apparently you want to make groups of Rooms that have the same value for property PostSubjectId AND have the same value for MemberId.Count().
var result = dbContext.Rooms.GroupBy(room => new
{
PostSubjectId = room.PostSucjectId,
MemberIdLength = room.MemberId.Count(),
});
The result is a sequence of groups of Rooms. Every group has a key, which is a combination of [PostSubjectId, MemberIdLength]. The group is a sequence of Rooms. All rooms in one group have the same combination of [PostSubjectId, MemberIdLength].
If you don't want a sequence of groups of Rooms, you can use the overload of GroupBy that has a parameter resultSelector
var result = dbContext.Rooms.GroupBy(
// parameter keySelector
room => new
{ PostSubjectId = room.PostSucjectId,
MemberIdLength = room.MemberId.Count(),
},
// parameter resultSelector:
// from every combination of [PostSubjectId, MemberIdLength] (= the key) and
// all rooms that have this combination, make one new object:
(key, roomsWithThisKey) => new
{
// select the properties that you actually plan to use, for example
PostSubjectId = key.PostSubjectId,
MemberIdLength = key.MemberIdLength,
RoomInformations = roomsWithThisKey.Select(roomWithThisKey => new
{
Id = roomWithThisKey.Id,
Member = roomWithThisKey.Member,
...
})
.ToList(),
});

Related

EF core - parent.InverseParent returns null for some rows

I have a Category table and it has a Parent Category, I try to iterate over all the categories and get the parents categories with it's Inverse Parent but some of them returns without the inverse parents from unknown reason.
Categories.cs
public partial class Categories
{
public Categories()
{
InverseParent = new HashSet<Categories>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime CreateDate { get; set; }
public bool? Status { get; set; }
public virtual Categories Parent { get; set; }
public virtual ICollection<Categories> InverseParent { get; set; }
}
This is how I try to iterate them to create a select list items:
var parentCategories = await _context.Categories.
Include(x => x.Parent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
So the problem is that some of my parent categories returns all their children categories and some don't and I don't why.
There are several issues with that code, all having some explaination in the Loading Related Data section of the documentation.
First, you didn't ask EF Core to include InverseParent, so it's more logically to expect it to be always null.
What you get is a result of the following Eager Loading behavior:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Second, since the query is changing it's initial shape (Select, Disctinct), it's falling into Ignored Includes category.
With that being said, you should build the query other way around - starting directly with parent categories and including InverseParent:
var parentCategories = await _context.Categories
.Include(x => x.InverseParent)
.Where(x => x.InverseParent.Any(c => c.Status == true)) // to match your query filter
.ToListAsync();
While you are including Include(x => x.Parent), you don't seem to do the same for InverseParent. This might affect your results exactly the way you describe. Would including it fix it?
parentCategories = await _context.Categories.
Include(x => x.Parent).
Include(x => x.InverseParent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
UPD: Since you are selecting x => x.Parent anyway it might be necessary to use ThenInclude() method instead.

IQueryable.Select to a List type sub POCO

I have an Entity Framework model in which there is a "Customers" and a "CustomerPhones" table. A customer can have multiple phone numbers so the "Customer" entity has a collection of "Phone". I can query the model with no problem :
using (CustomerEntities context = new CustomerEntities())
{
Customer customer = context.Customers.FirstOrDefault();
CustomerPhone phone = customer.Phones.FirstOrDefault();
MessageBox.Show(customer.Name + " " + phone.Number);
}
The model is too complex for what I need to do (even though my example is basic) so I'm trying to boil it down to simpler POCOs. Here are the 2 simple classes :
public class SimplePhone
{
public int Id { get; set; }
public string Number { get; set; }
}
public class SimpleCustomer
{
public int Id { get; set; }
public string Name { get; set; }
//Phones is a list because a single Customer can have multiple phone numbers
public List<SimplePhone> Phones { get; set; }
}
I can populate the simple properties of the object using the "Select" method of "IQueryable" :
using (CustomerEntities context = new CustomerEntities())
{
IQueryable<SimpleCustomer> customers = context.Customers.Select(
c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name
}
);
SimpleCustomer customer = customers.FirstOrDefault();
MessageBox.Show(customer.Name);
}
So my question is pretty simple : how can I populate the "Phones" property which is a list?
using (CustomerEntities context = new CustomerEntities())
{
IQueryable<SimpleCustomer> customers = context.Customers.Select(
c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name
Phones = ///????
}
);
SimpleCustomer customer = customers.FirstOrDefault();
SimplePhone phone = customer.Phones.FirstOrDefault();
MessageBox.Show(customer.Name + " " + phone.Number);
}
Let me know if I'm unclear and/or you need more details.
Thanks!
I'm not sure if there isn't something more to your question, but as far as I understand, you can just call ToList and it will be materialized as a list:
IQueryable<SimpleCustomer> customers =
context.Customers.Select(c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name,
Phones = c.Phones.Select(p => new SimplePhone
{
Id = p.Id, // Unless you want the custom Id, i.e. c.Id
Number = p.Number
}).ToList();
});

How to perform a left outer join using Entity Framework using navigation properties

I'm trying to find out how I would define the code first navigation properties on these two classes to perform something similiar to this query:
SELECT USERID, FIRSTNAME, LASTNAME, COURSEID, NAME
FROM User
LEFT OUTER JOIN Course ON User.USERID = Course.USERID
WHERE COURSEID = 1
So I'm trying to find a list of users together with if they have attended a certain course.
public class User
{
public int UserId {get;set; }
public string FirstName {get;set;}
public string LastName {get;set;}
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get;set; }
public int UserId { get;set; }
public string Name { get;set; }
public virtual User User {get;set;}
}
If I was to write a query to achieve this
var u = Users.Where(x => x.Courses.Any(x => x.CourseId = 1));
This does a subquery, which is not what I wanted (as people who didnt attend the course would not show).
How would the navigation property be defined?
HasMany(t => t.Courses).WithOptional(t => t.User).HasForeignKey(t => t.UserId);
Check this link:
http://msdn.microsoft.com/en-us/library/vstudio/bb397895.aspx
Left outer joins in LINQ are done via DefaultIfEmpty method.
var u = Users.Select ( x => new {
User = x,
AttendedCourse = x.Courses.Any()
} );
For specific course id,
var u = Users.Select ( x => new {
User = x,
AttendedCourse = x.Courses.Any( c => c.CourseID == 1 )
} );
Sub query is the only way to write related queries, However, EF will choose the best suitable join type and will give you correct results. And EF can manage mostly all kinds of queries without doing joins.

how to read data from junction table code first approach

how can I retrieve data from junction table in Code first approach.i know i can add data like this.
Movie.Tag.Add(item)
but want to know how to get back those data in the junction table
This gives you all rows in the junction table - each row is represented as an anonymous object with the two Ids of the row as properties:
var junctionTableDataList = (from m in context.Movies
from t in m.Tags
select new
{
MovieId = m.MovieId,
TagId = t.TagId
}).ToList();
You can add a where clause before the select if you want to limit the result to a specific or a few movies.
Edit
The same with extension methods and lambda expressions would look like this:
var junctionTableDataList = context.Movies
.SelectMany(m => m.Tags.Select(t => new
{
MovieId = m.MovieId,
TagId = t.TagId
}))
.ToList();
Edit 2
If you want return the data from a method you can create a litte helper type and return a list of those "named" objects instead of anonymous objects:
public class JunctionData
{
public int MovieId { get; set; }
public int TagId { get; set; }
}
List<JunctionData> junctionTableDataList
= (from m in context.Movies
from t in m.Tags
select new JunctionData
{
MovieId = m.MovieId,
TagId = t.TagId
}).ToList();
Or ...Select(t => new JunctionData... for the extension method syntax.

Receive Dictionary<int, string> from linq to entity?

continuation of the issue How get array in linq to entity?
but now is not array => Dictionary
City type is Dictionary
var sites = (from country in db.Countries
select new
{
Country = country.Title,
Cities = country.Cities.Select(m => m.Title)
})
.AsEnumerable()
.Select(country => new SitiesViewByUser()
{
Country = country.Country,
City = country.Cities.ToArray()
});
update:
public class SitiesViewByUser
{
public string Country { get; set; }
public Dictionary<int, string> City { get; set; }
}
You can use ToDictionary to create a dictionary from a LINQ sequence.
The first part is the key, and the second the value, E.g.
.Select(country => new SitiesViewByUser()
{
Country = country.Country,
City = country.Cities.ToDictionary(c => c, c => c);
});
This assumes that City on SitiesViewByUser is defined as Dictionary<string, string>
Your LINQ is quite confusing though. You are creating an anonymous type, asssumed to shape a Country, but which has a Country property on it, which is infact the Title of the country (is that the name of the country?).
You also have a collection of just the city Titles, so I'm not sure what value you are going to use in your City dictionary on your SitiesViewByUser type.
Also, what is a Sitie? :\
Update
You could do something like this:
var countries = (from country in db.Countries
select new
{
Title = country.Title,
CityIds = country.Cities.Select(c => c.Id),
CityTitles = country.Cities.Select(c => c.Title)
}).AsEnumerable();
// You have a collection of anonymous types at this point,
// each type representing a country
// You could use a foreach loop to generate a collection
// of SitiesViewByUser, or you could use LINQ:
var sitiesViewByUsers = countries.Select(c => new SitiesViewByUser
{
Country = c.Title,
City = c.CityIds.Zip(c.CityTitles, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value)
};
Alternatively, why don't you change the SitiesViewByUser type to be:
public class SitiesViewByUser
{
public string Country { get; set; }
public IEnumerable<City> Cities { get; set; }
}
Then you can do (using fluent syntax):
var sitiesViewByUsers = db.Countries.Select(c => new SitiesViewByUser
{
Country = c.Title,
Cities = c.Cities
});