LINQ To Entities INNER JOIN with COUNT - c#

I'm trying to do replicate the following query in LINQ using the comprehension method. My SQL is as follows:
SELECT COUNT(Post.Id), User.Id, User.Name
FROM Post
INNER JOIN User ON Post.ModeratorId = User.Id
WHERE Post.Status IN ("Live", "Pending", "Expired")
GROUP BY User.Id, User.Name
My LINQ query is as follows but its still returns a 0 count when no moderator has been assigned to the post. Note a Post.ModeratorId is a nullable value.
I only want a list of moderators and a count of post they are or have moderated.
How can I replicate the above SQL in LINQ?
public IEnumerable<ModeratorPostViewModel> GetModeratorPostCount()
{
var posts = _context.Post
.Include(p => p.Moderator)
.Include(p => p.Status)
.Where(p => p.ModeratorId != null && p.Status.Name IN ("Live", "Pending", "Expired"))
.OrderBy(p => p.Moderator.Name)
.Select(p => new ModeratorPostViewModel
{
Id = p.ModeratorId,
Name = p.Moderator.Name,
CountOfPosts = p.Moderator.ModeratorPosts.Count()
})
.ToList();
// Return list
return posts
}
My models are defined as follows:
public class Post
{
int Id { get; set; }
int StatusId { get; set; }
string ModeratorId { get; set; }
// Navigation properties
public Status Status { get; set; }
public ApplicationUser Moderator { get; set; }
// some other other properties
}
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
// Navigation property
public ICollection<Post> ModeratorPosts { get; set; }
}

I only want a list of moderators and a count of post they are or have moderated
Then base your query on Moderator (or whatever it's called) entity:
var statuses = new [] { "Live", "Pending", "Expired" };
var posts = _context.Moderator
.OrderBy(m => m.Name)
.Select(m => new ModeratorPostViewModel
{
Id = m.Id,
Name = m.Name,
CountOfPosts = m.ModeratorPosts.Count(p => statuses.Contains(p.Status.Name))
})
.Where(m => m.CountOfPosts != 0)
.ToList();
UPDATE: I have to admit that the above LINQ query does not produce a very good SQL, especially with the last Where condition. So you might resort to the exact LINQ equivalent of your SQL query (you missed the GROUP BY part):
var statuses = new [] { "Live", "Pending", "Expired" };
var posts = _context.Post
.Where(p => p.ModeratorId != null && statuses.Contains(p.Status.Name))
.GroupBy(p => new { Id = p.ModeratorId, Name = p.Moderator.Name })
.Select(g => new ModeratorPostViewModel
{
Id = g.Key.Id,
Name = g.Key.Name,
CountOfPosts = g.Count()
})
.OrderBy(m => m.Name)
.ToList();

You can do a group by after filtering the posts by the statuses you are looking for
var s = new List<string>() {"Live", "Pending", "Expired"};
var grouped = db.Post.Where(x => s.Contains(x.Status)) //Fitler for the statuses
.GroupBy(f => f.ModeratorId, po => po,
(k, items) => new ModeratorPostViewModel
{
Name = items.FirstOrDefault().User.Name,
Id=k,
CountOfPosts = items.Count()
}).ToList();

Related

Join two tables and filter to retrieve Name

This code is performing well as expected.
var soldQtyForEachItem = await _context.InvoiceProduct
.Where(x => x.ProductId != 0)
.GroupBy(x => x.ProductId)
.Select(grp => new CompanyProducts
{
CompanyProductId = grp.Key,
CompanyProductSoldQuantity = grp.Sum(item => item.QuantitySold)
}).ToListAsync();
Question :
I now need to join another table called Products and filter by Id against InvoiceProduct table and retrieve
ProductsItemName which is a row from Products table; then all need to go to a custom type "CompanyProducts" below.
Please how do I achieve it?
public class CompanyProducts
{
public int CompanyProductId { get; set; }
public int CompanyProductName { get; set; }
public int CompanyProductSoldQuantity { get; set; }
}
InvoiceProduct table is a many to many, meaning that one InvoiceId may have multiple ProductIds.
Products table has property like ProductId, ProductsItemName.
Simplest solutions is to include ProductItemName into grouping:
var soldQtyForEachItem = await _context.InvoiceProduct
.Where(x => x.ProductId != 0)
.GroupBy(x => new { x.ProductId, x.Product.ProductsItemName } )
.Select(grp => new CompanyProducts
{
CompanyProductId = grp.Key.ProductId,
CompanyProductName = grp.Key.ProductsItemName,
CompanyProductSoldQuantity = grp.Sum(item => item.QuantitySold)
})
.ToListAsync();

How to convert Generic List<anonymous type > to Generic List <SubToSubMenu>?

I have a problem with type convert var to List<SubToSubMenu>.
Firstly, I select data from database that is ok !!!!. I received data with var variable, but i can't convert var type to List<SubToSubMenu> data type.
This is my LINQ statement:
var ss =
db
.SubToSubMenus
.Join(
db.MenuPermissions,
s => s.ID,
p => p.SubToSubMenuId,
(s, p) => new { s, p })
.Where(w => w.s.Active == true && w.p.RoleId == roleId && w.p.hasPermission == true)
.Select(s => new
{
ID = s.s.ID,
SubToSubMenuName = s.s.SubToSubMenuName,
Description = s.s.Description,
})
.ToList();
This is SubToSubMenu class:
[Table("SubToSubMenus")]
public class SubToSubMenu : AceEntityBase
{
public SubToSubMenu()
{ }
[Key]
public string ID { get; set; }
public string SubToSubMenuName { get; set; }
public string Description { get; set; }
public string SubMenuID { get; set; }
}
var is not a type, it's syntactic sugar. You have an anonymous type that has no relation whatsoever to your SubToSubMenu type.
Instead of projecting into an anonymous type:
.Select(s => new { ... })
Project into the type you want:
.Select(s => new SubToSubMenu { ... })
You shouldn't need to project here. You're already selecting the SubToSubMenu from the database, so there should be no need to 'recreate' the class later in the expression chain.
var ss =
db
.SubToSubMenus
.Join(
db.MenuPermissions,
s => s.ID,
p => p.SubToSubMenuId,
(s, p) => new { s, p })
.Where(w => w.s.Active == true && w.p.RoleId == roleId && w.p.hasPermission == true)
This is good so far. You've joined two tables and applied the correct filters.
.Select(s => new
{
ID = s.s.ID,
SubToSubMenuName = s.s.SubToSubMenuName,
Description = s.s.Description,
})
.ToList();
OK stop here. If the ultimate goal of this query is to select only SubToSubMenu entities, you can replace this part with just
.Select(s => s.s);
...and ignore the rest of the subsequent statements.
However, you could also go one step further and make the association between the SubToSubMenu and MenuPermissions entities implicit in your EF configuration, so you'll have no need to .Join in LINQ. Given this, the eventual query should be similar to:
var ss = db.SubToSubMenus
.Where(stsm => stsm.Active
&& stsm.MenuPermissions.RoleId == roleId
&& stsm.MenuPermissions.HasPermission);
Try this:
var ss =
db
.SubToSubMenus
.Join(
db.MenuPermissions,
s => s.ID,
p => p.SubToSubMenuId,
(s, p) => new { s, p })
.Where(w => w.s.Active == true && w.p.RoleId == roleId && w.p.hasPermission == true)
.Select(s => new
{
ID = s.s.ID,
SubToSubMenuName = s.s.SubToSubMenuName,
Description = s.s.Description,
})
.ToList()
.Select(s => new SubToSubMenu()
{
ID = s.ID,
SubToSubMenuName = s.SubToSubMenuName,
Description = s.Description,
})
.ToList();
I have added a simple projection to the end of your query. This is to keep the code as close to how you had it to begin with and to aid with any future refactoring.
In this case, it can certainly be coded as a single projection.

Selecting specific columns from GroupBy list

Model:
public class Ticket {
public Ticket();
public int Id { get; set; }
public virtual TicketUrgency TicketUrgency { get; set; }
public int UrgencyId { get; set; }
}
public class TicketUrgency {
public TicketUrgency();
[Key]
public int Id { get; set; }
[MaxLength(50)]
[Required]
public string Name { get; set; }
public ICollection<Ticket> Tickets { get; set; }
}
I have the following linq statement:
var model = Entities
.Include(x => x.TicketUrgency)
.GroupBy(x => x.UrgencyId)
.Select(g => new {
id = g.Key,
count = g.Count(),
name = g.FirstOrDefault(u => u.UrgencyId == g.Key).TicketUrgency.Name
});
I want to Group Entities by UrgencyId and then return the Key (UrgencyId), and also count of the items in a single group and show the name of the Urgency.
When I run it, the query just gets hung up without any exceptions.
This should work, doing it the other way around, by retrieving all TicketUrgencies first and grouping it.
Entities.Include(e => e.Tickets)
.GroupBy(t => t.Id)
.Select(g => new {
id = g.Key,
name = g.FirstOrDefault().Name,
count = g.FirstOrDefault().Tickets.Count()
});
Very simple. Just try this :
var model = Entities
.Include(x => x.TicketUrgency)
.GroupBy(x => new {UrgencyId = x.UrgencyId ,
Name = x.TicketUrgency.Name})
.Select(x=> new { UrgencyId = x.Key.UrgencyId,
Name = x.Key.Name,
Count = x.Count()});
Since you are grouping by UrgencyId, you know all members of g have the same id as the Key, so to pick up the name just pull the first one. You also know g isn't empty because that wouldn't make a group:
var model = Entities
.Include(x => x.TicketUrgency)
.GroupBy(x => x.UrgencyId)
.Select(g => new {
id = g.Key,
name = g.First().TicketUrgency.Name
count = g.Count(),
});
You could group by those two properties:
var model = Entities
.Include(x => x.TicketUrgency)
.GroupBy(x => new{ x.UrgencyId, x.TicketUrgency.Name })
.Select(g => new {
id = g.Key.UrgencyId,
count = g.Count(),
name = g.Key.Name
});
Another way could be, as #ASpirin suggested,starting the query from TickerUrgency:
var result= TicketUrgencies.Include(t=>t.Tickets)
.Where(t=>t.Tickets.Any())
.Select(t=> new {id=t.Id,name=t.Name, count= t.Tickets.Count()})

Order by array values in Linq to Entity Framework Query

I am trying to write an OrderBy clause in a Linq to EntityFramework query. My problem is that the entity table I am looking at stores an ID, that relates to a table in a different database and I cannot adjust the database.
MainDatabase.EntityToOrder
ID
Name
OtherID
SecondDatabase.OtherEntity
ID
Name
My C# EntityToOrder Model looks like this, and I need to be able to order by "OtherName"
EntityToOrder.cs
public class EntityToOrder
{
[DataMember]
public long ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public long OtherId { get; set; }
public string OtherName { get; set; }
}
So, I would like to Order EntityToOrder by "OtherName" in the most efficient way possible. My existing query looks like this.
var entities = mainContext.EntityToOrder.OrderBy(e => e.Name).Skip(startIndex).Take(pageSize).ToList();
var otherIds = entities.Select(e => e.OtherID).ToList();
Dictionary<long, string> otherNames = secondContext.OtherEntity
.Where(oe => otherIds.Contains(oe.ID))
.Select(oe => new { ID = oe.ID, Name = oe.Name })
.ToDictionary(oe => oe.ID, oe => oe.Name);
entities.ForEach(e => OtherName = otherNames[e.OtherID]);
How can I write the most efficient query to order by "OtherName", preferably avoiding selecting the whole EntityToOrder table into memory.
Update
For clarity, here is some code that achieves the OrderBy, but needs to retrieve the entire EntityToOrder table into memory. I was hoping this could be achieved in a more efficient way. Also, the OtherEntity can belong to many EntityToOrder rows.
var entities = mainContext.EntityToOrder.ToList();
var otherIds = entities.Select(e => e.OtherID).ToList();
Dictionary<long, string> otherNames = secondContext.OtherEntity
.Where(oe => otherIds.Contains(oe.ID))
.Select(oe => new { ID = oe.ID, Name = oe.Name })
.ToDictionary(oe => oe.ID, oe => oe.Name);
entities.ForEach(e => OtherName = otherNames[e.OtherID]);
return entities.OrderBy(e => e.OtherName).Skip(startIndex).Take(pageSize).ToList();
Quite challenging task. I was thinking initially just to switch the roles and perform pagination (OrderBy/Skip/Take) on OtherEntity table, but unfortunately that doesn't work due to one to many relationship. So I ended up with doing some pre pagination in memory on OtherEntity. However, in order to do that I needed counts of the matching items in EnityToOrder, so this is retrieved with additional db query, which makes the solution involving 3 db queries and some memory processing. Here it is
var countByOtherId = db.EntityToOrder
.GroupBy(e => e.OtherId)
.Select(g => new { ID = g.Key, Count = g.Count() })
.ToDictionary(e => e.ID, e => e.Count);
var other = new Dictionary<long, string>();
int skipCount = startIndex, useCount = 0;
foreach (var e in db.OtherEntity.OrderBy(e => e.Name))
{
int count;
if (!countByOtherId.TryGetValue(e.ID, out count)) continue;
if (skipCount > 0 && other.Count == 0)
{
if (skipCount >= count) { skipCount -= count; continue; }
count -= skipCount;
}
other.Add(e.ID, e.Name);
if ((useCount += count) >= pageSize) break;
}
var entities = db.EntityToOrder
.Where(e => other.Keys.Contains(e.OtherId))
.AsEnumerable()
.Select(e => new EntityToOrder { ID = e.ID, Name = e.Name,
OtherId = e.OtherId, OtherName = other[e.OtherId] })
.OrderBy(e => e.OtherName).ThenBy(e => e.Name)
.Skip(skipCount).Take(pageSize)
.ToList();
Now, I'm not quite sure if that's better to what are you doing currently, but it's worth trying.
If you can change the model, then you might try the following:
public class EntityToOrder
{
[DataMember]
public long ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public long OtherId { get; set; }
[ForeignKey("OtherId")]
public OtherEntity OtherEntity{ get; set; }
}
Then, you should be able to perform this query:
using System.Data.Entity;
var entities = mainContext
.EntityToOrder
.Include(x => x.OtherEntity)
.OrderBy(e => e.OtherEntity.Name)
.Skip(startIndex)
.Take(pageSize)
.ToList();
Edit :
Sorry, I missed the point that you had 2 databases....
I found an alternative which I thought I would post in case it is useful to anyone. I used a .Join() to merge the dictionary of OtherEntity into my query. This still selects into an IEnumerable so I don't think it is more efficient.
var entities = mainContext.EntityToOrder;
var otherIds = entities.Select(e => e.OtherID).ToList();
Dictionary<long, string> otherNames = secondContext.OtherEntity
.Where(oe => otherIds.Contains(oe.ID))
.Select(oe => new { ID = oe.ID, Name = oe.Name })
.ToDictionary(oe => oe.ID, oe => oe.Name);
Func<EntityToOrder, KeyValuePair<long, string>, EntityToOrder> joinFunc = ((a, b) => {
a.OtherName= b.Value;
return a;
});
return entities.Join(otherNames, e => e.OtherID, oe => oe.Key, joinFunc)
.OrderBy(e => e.OtherName)
.Skip(startIndex)
.Take(pageSize)
.ToList();
Note on Includes
When applying Join you select into an IEnumerable and therefore lose the ability to access properties from a linked table. To counter this you would need to add a .Include() for any linked table you need to access before applying the .Join(). E.g.
var entities = mainContext.EntityToOrder
.Include("LinkedEntity");
return entities.Join(otherNames, e => e.OtherID, oe => oe.Key, joinFunc)
.OrderBy(e => e.OtherName)
.ThenBy(e => e.LinkedEntity.Name) //reference to linked table
.ToList();

Linq How to perform Subquery with max function that returns a primitive value such as an integer?

I have the below query. :
List<MyItem> myList
= randomEntity
.GroupBy(x => new {
x.Id,
x.randomInnerEntity.Name,
x.randomInnerEntity.Number
})
.Select(z => new MyItem
{
Id = z.Key.Id,
Name = z.Key.Name,
Totals = z.Count(),
LatestObj = randomEntity.Where(x => x.Id == z.Key.Id)
.GroupBy(x => x.Id)
.Select(gr => new {
item1 = gr.Key,
item2 = gr.Max(x => x.SomeInteger.Value)
})
})
.OrderBy(z => z.Name)
.ToList();
As you can see the inner subquery, that provides a dynamic object of LatestObj works but it provides an anonymous object. I was wondering how to perform the same query but return a primitive type like integer.
***As requested
public class MyItem
{
property Guid? Id { get; set; }
property String Name { get; set; }
property Int MaxValueTryingToGet { get; set; } //This is the field I would like to set with the integer max
property Int Totals { get; set; }
property dynamic LatestObj { get; set; }
property randInner randomInnerEntity { get; set; }
}
LatestObj = randomEntity.Where(x => x.Id == z.Key.Id)
.GroupBy(x => x.Id)
.Select(gr => new {
item1 = gr.Key,
item2 = gr.Max(x => x.SomeInteger.Value)
})
Since you are just selecting items where the item id matches z.Key.Id, there does not seem to be much point grouping by id.
You can should be able to get the maximum value directly by using
LatestObj = randomEntity.Where(x => x.Id == z.Key.Id).Max(x -> x.SomeInteger.Value)

Categories

Resources