I have the following simple statement in my Entity Framework code:
query = query
.Where(c => c.NotificationType == NotificationType.AppMessage)
.GroupBy(c => c.ConversationId)
.Select(d => d.OrderByDescending(p => p.DateCreated).FirstOrDefault());
It simply finds the latest Notification based on a group by with conversationId and select latest. Easy.
However, this is ONLY what I want if c.NotificationType == NotificationType.AppMessage. If the column is different than AppMessage (c.NotificationType <> NotificationType.AppMessage), I just want the column. What I truly Want to write is a magical statement such as:
query = query
.Where(c => (c.NotificationType <> NotificationType.AppMessage)
|| ((c.NotificationType == NotificationType.AppMessage)
.GroupBy(c => c.ConversationId)
.Select(d => d.OrderByDescending(p => p.DateCreated).FirstOrDefault()));
But this doesn't make sense because the GroupBy/Select is based on the first where statement.
How do I solve this?
The simplest way is to compose UNION ALL query using Concat at the end of your original query:
query = query
.Where(c => c.NotificationType == NotificationType.AppMessage)
.GroupBy(c => c.ConversationId)
.Select(d => d.OrderByDescending(p => p.DateCreated).FirstOrDefault())
.Concat(query.Where(c => c.NotificationType != NotificationType.AppMessage));
public class EntityClass
{
public int NotificationType { get; set; }
public int ConversationId { get; set; }
public DateTime Created { get; set; }
public static EntityClass GetLastNotification(int convId)
{
var list = new List<EntityClass>(); // Fill the values
list = list
.GroupBy(i => i.ConversationId) // Group by ConversationId.
.ToDictionary(i => i.Key, n => n.ToList()) // Create dictionary.
.Where(i => i.Key == convId) // Filter by ConversationId.
.SelectMany(i => i.Value) // Project multiple lists to ONLY one list.
.ToList(); // Create list.
// Now, you can filter it:
// 0 - NotificationType.AppMessage
// I didn't get what exactly you want to filter there, but this should give you an idea.
var lastNotification = list.OrderByDescending(i => i.Created).FirstOrDefault(i => i.NotificationType == 0);
return lastNotification;
}
}
you filter your list with "GroupBy" based on ConversationId. Next, create a dictionary from the result and make only one list (SelectMany). Then, you already have one list where should be only records with ConversationId you want.
Last part is for filtering this list - you wanted to last notification with certain NotificationType. Should be working :)
Related
This is an entity
public class ViewModel
{
public string Id { get; set; }
public DateTime Start { get; set; }
public string Name { get; set; }
}
This is my context query,works with ef core in dbcontext.
var list = _Service.GetDataByMonth(start,end).ToList();
// it returns all entity between giving start, end param.
// start and end is Datetime property comes from ajax. This code works fine it return almost 1k objectlist with type of ViewModel
like
[0] Id="1",Title="sample",Start:""15.12.2020"
[1] Id="2",Title="sample2",Start:""15.12.2020"
[2] Id="3",Title="sample3",Start:""16.12.2020"
[3] Id="4",Title="sample4",Start:""16.12.2020"
As shows above we got almost 20+ entity per day.
I can get count per day like
var listt = _Service.GetDataByMonth(start,end).GroupBy(x => x.Start.Date).Select(grp => new { Date = grp.Key, Count = grp.Count() });
[0] Key:""15.12.2020",Count:20
[1] Key:""16.12.2020",Count:25
[2] Key:""17.12.2020",Count:44
it returns like this.
So what i want is giving start and end param a funciton then get 3 values per day between giving datetime object
NEW
var list1= _Service.GetDataByMonth(start,end).GroupBy(x => x.StartDate.Date)
.Select(grp => grp.Take(3)).ToList();
//this type List<Ienumerable<Viewmodel>>
var list2 = _Service.GetDataByMonth(start,end).GroupBy(x => x.StartDate.Date).Select(grp => grp.Take(3).ToList()).ToList();
// this type List<List<Viewmodel>>
My want it List<Viewmodel>
service
...
return entity.Where(x =>
x.IsDeleted == false &&
(x.StartDate.Date >= start.Date && x.StartDate.Date <=end.Date)
).OrderBy(x => x.FinishDate).ToList();
// it work with this way but bad way
var lis = list.SelectMany(d => d).ToList();
Yes I figuredout using selectmany instead of select works fine. Thank you again
You can use .Take() to only take 3 items of each group:
_Service.GetDataByMonth(start,end)
.GroupBy(x => x.Start.Date)
.Select(grp => new { Data = grp.Take(3).ToList(), Date = grp.Key, Count = grp.Count() })
.ToList();
var list1= _Service.GetDataByMonth(start,end).GroupBy(x => x.StartDate.Date)
.Select(grp => grp.Take(3)).ToList();
It returns List<List> maybe it will help some one bu my need is comes from with this code
This code makes list of list each element is list and contians 3 object.
var list1= _Service.GetDataByMonth(start,end).GroupBy(x => x.StartDate.Date)
.SelectMany(grp => grp.Take(3)).ToList();
Many makes just list. It mades list with ordered objects
Consider the following LINQ statements:
var model = getModel();
// apptId is passed in, not the order, so get the related order id
var order = (model.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => y.OrderId));
var orderId = 0;
var orderId = order.LastOrDefault();
// see if more than one appt is associated to the order
var apptOrders = (model.getMyData
.Where(x => x.OrderId == orderId)
.Select(y => new { y.OrderId, y.AppointmentsId }));
This code works as expected, but I could not help but think that there is a more efficient way to accomplish the goal ( one call to the db ).
Is there a way to combine the two LINQ statements above into one? For this question please assume I need to use LINQ.
You can use GroupBy method to group all orders by OrderId. After applying LastOrDefault and ToList will give you the same result which you get from above code.
Here is a sample code:
var apptOrders = model.getMyData
.Where(x => x.ApptId == apptId)
.GroupBy(s => s.OrderId)
.LastOrDefault().ToList();
Entity Framework can't translate LastOrDefault, but it can handle Contains with sub-queries, so lookup the OrderId as a query and filter the orders by that:
// apptId is passed in, not the order, so get the related order id
var orderId = model.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => y.OrderId);
// see if more than one appt is associated to the order
var apptOrders = model.getMyData
.Where(a => orderId.Contains(a.OrderId))
.Select(a => a.ApptId);
It seems like this is all you need:
var apptOrders =
model
.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => new { y.OrderId, y.AppointmentsId });
I'm trying to make a linq using where, group by and select at same time but I cannot do it works and it always throws an exception.
How could I do it works ?
Linq
public ActionResult getParceiros(){
//return all partners
IList<ViewParceirosModel> lista = new List<ViewParceirosModel>();
lista = context.usuarios.Where(u => u.perfil == RoleType.Parceiro)
.Select(x => new ViewParceirosModel
{
id = x.id,
nomeParceiro = x.nome,
emailAcesso = x.email
})
.GroupBy(x => x.id)
.OrderBy(x => x.nomeParceiro)
.ToList();
return View(lista);
}
Exception
Your program doesn't do what you want. Alas you forgot to tell you what you want, you only showed us what you didn't want. We'll have to guess.
So you have a sequence of Usarios.
IQueryable<Usario> usarios = ...
I don't need to know what a Usario is, all I need to know is that it has certain properties.
Your first step is throwing away some Usarios using Where: you only want to keep thos usarios that have a Perfil equal to RoleType.Parceirdo:
// keep only the Usarios with the Perfil equal to RoleType.Parceirdo:
var result = usarios.Where(usario => usario.Perfil == RoleType.Parceirdo)
in words: from the sequence of Usarios keep only those Usarios that have a Perfil equal to RoleTyoe.Parceirdo.
The result is a subset of Usarios, it is a sequence of Usarios.
From every Usario in this result, you want to Select some properties and put them into one ViewParceirosModel:
var result = usarios.Where(usario => usario.Perfil == RoleType.Parceirdo)
.Select(usario => new ViewParceirosModel
{
Id = x.id,
NomeParceiro = x.nome,
EmailAcesso = x.email,
})
In words: from every Usario that was kept after your Where, take the Id, the Nome and the Email to make one new ViewParceirosModel.
The result is a sequence of ViewParceirosModels. If you add ToList(), you can assign the result to your variable lists.
However your GroupBy spoils the fun
I don't know what you planned to do, but your GroupBy, changes your sequence of ViewParceirosModels into a sequence of "groups of ViewParceirosModels" Every ViewParceirosModel in one group has the same Id, the value of this Id is in the Key.
So if after the GroupBy you have a group of ViewParceirosModel with a Key == 1, then you know that every ViewParceirosModel in this group will have an Id equal to 1.
Similarly all ViewParceirosModel in the group with Key 17, will have an Id equal to 17.
I think Id is your primary key, so there will only be one element in each group. Group 1 will have the one and only ViewParceirosModel with Id == 1, and Group 17 will have the one and only ViewParceirosModel with Id == 17.
If Id is unique, then GroupBy is useless.
After the GroupBy you want to Order your sequence of ViewParceirosModels in ascending NomeParceiro.
Requirement
I have a sequence of Usarios. I only want to keep those Usarios with a Perfil value equal to RoleType.Parceirdo. From the remaining Usarios, I want to use the values of properties Id / Nome / Email to make ViewParceirosModels. The remaining sequence of ViewParceirosModels should be ordered by NomeParceiro, and the result should be put in a List.
List<ViewParceirosModel> viewParceiroModels = Usarios
.Where(usario => usario.Perfil == RoleType.Parceirdo)
.Select(usario => new ViewParceirosModel
{
Id = x.id,
NomeParceiro = x.nome,
EmailAcesso = x.email,
}
.OrderBy(viewParceirosModel => viewParceirosModel.NomeParceiro)
.ToList();
When you create a LINQ query with group by clause, you receive as result a grouped query.
It is a kind of dictionary that has as key the field you chose to group and as value a list of records of this group.
So, you cannot order by "nomeParceiro" because this field is inside the group.
If you detail how you expect the result I can show you a code example for this.
You can find more details in this section of the doc: https://learn.microsoft.com/pt-br/dotnet/csharp/linq/group-query-results
Let's say ViewParceirosModel look like
public class ViewParceirosModel
{
public int id {get; set;}
public List<string> nomeParceiro {get; set;}
public List<string> emailAcesso {get; set;}
}
After that, you can Groupby then select combine with Orderby like below
IList<ViewParceirosModel> lista = new List<ViewParceirosModel>();
lista = context.usuarios.Where(u => u.perfil == RoleType.Parceiro)
.Select(x => new ViewParceirosModel
{
id = x.id,
nomeParceiro = x.nome,
emailAcesso = x.email
})
.GroupBy(x => x.id)
.Select(g => new ViewParceirosModel
{
id = g.Key,
nomeParceiro = g.Select(p => p.nomeParceiro).OrderBy(x => x.nomeParceiro).ToList()
nomeParceiro = g.Select(p => p.emailAcesso).ToList()
})
.ToList();
You can use the following code.
IList<ViewParceirosModel> lista = new List<ViewParceirosModel>();
lista = context.usuarios.Where(u => u.perfil == RoleType.Parceiro)
.Select(x => new ViewParceirosModel
{
id = x.id,
nomeParceiro = x.nome,
emailAcesso = x.email
})
.OrderBy(x => x.nomeParceiro)
.GroupBy(x => x.id)
.ToList();
or
List<List<ViewParceirosModel>> listb = context.usuarios
.Where(u => u.perfil == RoleType.Parceiro)
.GroupBy(g => g.id).OrderBy(g => g.Key)
.Select(g => g.OrderBy(x => x.nomeParceiro)).ToList();
I'm trying to build a change log query that pulls logs since last revision.
I now need to consolidate the logs based on some rules.
Here's my Changelog Model:
public abstract class ChangeLogObject : TrackedObject {
/// <summary>
/// Type of transaction that occured
/// </summary>
public ChangeType ChangeType { get; set; }
/// <summary>
/// Original Id of the stateful object this changelog was created against
/// </summary>
public long EntityId { get; set; }
}
public enum ChangeType {
Created,
Updated,
Deleted
}
So the steps I need to follow are:
Get where Id > last revision
Group By EntityId
Don't bring back any groups where the entity has ChangeType.Created and ChangeType.Deleted
Don't bring back ChangeType.Updated records if there's a ChangeType.Deleted but no ChangeType.Created (remove updated records from the group)
Only bring back the last ChangeType.Updated record if there are multiples
If the group contains ChangeType.Created and ChangeType.Updated then only bring back the last ChangeType.Updated but change it to ChangeType.Created
This is how far I've got but I don't know how to 'bring back part of a group':
var newChanges =
// 1. Get where greater than last revision
srcSet.Where(x => x.Id > lastRevision)
// 2. Group by EntityId
.GroupBy(x => x.EntityId)
// 3. Don't bring back created and deleted (it's come and gone)
.Where(x => !(x.Any(y => y.ChangeType == ChangeType.Created) &&
x.Any(y => y.ChangeType == ChangeType.Deleted)))
// 4. Only bring back Deleted if no created (don't edit something you're about to delete)
.Where(x => (!x.Any(y => y.ChangeType == ChangeType.Created) &&
x.Any(y => y.ChangeType == ChangeType.Deleted)))
//create new grouping, but only use the delete record????
//convert back to individual change logs
.Select(x => x.ToList()
//maybe order by ID?
.OrderBy(y => y.Id));
For 4. How do I turn x into a new group with only the records/values that are ChangeType.Deleted if the group itself doesn't contain a ChangeType.Created but may contain ChangeType.Updated?
Some assumptions I've made:
srcSet is a a set of ChangeLogObject items.
As per requirement 5 in your list, to enable the retrieval of the "last" item, you should be able to sort the list by some value. For this I've introduced a ChangeDate field, which is the date on which the change occurred.
In my sample, TrackedObject looks like the below:
public abstract class TrackedObject
{
public int Id { get; set; }
public DateTime ChangeDate { get; set; }
}
I've broken down each step of the query and numbered them according to the requirement:
var lastRevision = 2;
var srcSet = new List<ChangeLogObject>();
// 1. & 2.
var entityGroupsByEntityId = srcSet
.Where(m => m.Id > lastRevision) // greater than last revision
//.OrderByDescending(m => m.ChangeDate)
.GroupBy(m => m.EntityId)
.Select(group => new
{
EntityId = group.Key,
ChangeCount = group.Count(),
Changes = group.ToList().OrderByDescending(m => m.ChangeDate)
});
// 3.
var entityGroupsWithNoDeleteAndCreate = entityGroupsByEntityId
.Where(group => !(group.Changes.Any(m => m.ChangeType == ChangeType.Created)
&& group.Changes.Any(m => m.ChangeType == ChangeType.Deleted))); // it doesn't contain both creates and deletes
// 4.
var entityGroupsWithoutDeletedAndNoCreate = entityGroupsWithNoDeleteAndCreate
.Where(group => !(group.Changes.Any(m => m.ChangeType == ChangeType.Deleted)
&& (group.Changes.Count(m => m.ChangeType == ChangeType.Created) < 1))); // it doesn't contain a delete without a create
// 5.
var entityGroupsWithUpdatedButNoCreated = entityGroupsWithoutDeletedAndNoCreate
.Where(group => group.Changes.Any(m => m.ChangeType == ChangeType.Updated)
&& !group.Changes.Any(m => m.ChangeType == ChangeType.Created)) // it updated but not created
.Select(group => new ChangeLogObject
{
EntityId = group.EntityId,
ChangeDate = group.Changes.First().ChangeDate,
ChangeType = group.Changes.First().ChangeType // don't change the type
});
// 6.
var entityGroupsWithCreatedAndUpdatedOnly = entityGroupsWithoutDeletedAndNoCreate
.Where(group => group.Changes.Any(m => m.ChangeType == ChangeType.Created)
&& group.Changes.Any(m => m.ChangeType == ChangeType.Updated)) // it contains both created and updated
.Select(group => new ChangeLogObject
{
EntityId = group.EntityId,
ChangeDate = group.Changes.First(m => m.ChangeType == ChangeType.Updated).ChangeDate, // bring back the first updated record
ChangeType = ChangeType.Created // change the type to created
});
// Join the 2 sets of Updated and Created changes
var finalResult = entityGroupsWithUpdatedButNoCreated.Union(entityGroupsWithCreatedAndUpdatedOnly).ToList();
Caveat:
The linq statements are not optimised for performance as these are not wrapped in IQueryable to ensure that these queries are evaluated on the database and not In-memory.
This should be enough to get you started on breaking down each part of your requirement into a query.
The following statement is not returning distinct values, but the whole list:
public ObservableCollection<MasterPartsList> ParentAssemblyBOM
{
get
{
var enumerable = this._parentAssemblyBOM
.Where(parent => parent.isAssy == true).Distinct();
return new ObservableCollection<MasterPartsList>(enumerable) ;
}
Truly, I should only be able to tell that the object is unique because this._parentAssemblyBOM.partNumber would be the distinct property. How do I work in this logic to yield the correct results?
Thanks in advance!
Try grouping by the identifier (in your case part number) and then select the first of the group:
var enumerable = this._parentAssemblyBOM
.Where(parent => parent.isAssy == true)
.GroupBy(x => x.partNumber)
.Select(x => x.FirstOrDefault());