Compare in Linq - c#

I have a bunch of data in a database that i want to write a search function for. The problem is that i'm getting many duplicates.
The data is structured in Names and Surnames and i want to only send one unique of both so if i have two people with the first name Foo, and surname Bar only one will show.
No matter how I think of it I always come back to that I need to compare them.
var names = db.People
.Where(r => r.Name.Contains(q))
.OrderBy(r=> r.Name)
*Psuedo-Code*
if((this.Name==next.Name)&&(this.surSame==next.Surname)
toss next data and loop to next
*Psuedo-Code*
.Take(5);
Maybe a bit messy, but you get the idea what I want to achieve. Can I do this in some way or is there any better way to go about it?

You could do this:
var names = db.People
.Where(r => r.Name.Contains(q))
.Select(r => new { Name = r.Name, Surname = r.Surname })
.Distinct()
.Take(5);
But if that won't work because you need the whole People record, you just want the first, I've done something like this with success:
var names = db.People
.Where(r => r.Name.Contains(q))
.GroupBy(r => new { Name = r.Name, Surname = r.Surname })
.Select(g => g.First())
.Take(5);

Distinct utilizing Equals on People class would be the correct way, but here's an alternative that is more "inline":
var names = db.People
.Where(r => r.Name.Contains(q))
.GroupBy(r => new { r.Name, r.Surname })
.Select(g => g.First())
.OrderBy(r => r.Name)
.Take(5);

Use Distinct() and implement the method Equals in People class, or use an auxiliary class to compare them:
public class PeopleComparer : IEqualityComparer<People>
{
public bool Equals(People x, People y)
{
return x.Name == y.Name && x.Surname == y.Surname;
}
public int GetHashCode(People obj)
{
unchecked
{
return (obj.Name.GetHashCode() * 31) + obj.Surname.GetHashCode();
}
}
}

Related

LINQ efficiency

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 });

How do I to use Where, Group By, Select and OrderBy at same query linq?

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();

NHibernate .SelectList() with List<string> in C#

I have a database request which looks as follows:
var userHasProfessions = xy.GetUserProfessions();
var users = sessionService.GetDefaultSession()
.Query<User>()
.Where(a => userProfessions.Contains(a.Profession.Id))
.ToList();
It gets all users that the requesting user is allowed to see, depending on his own profession. Now I wanna restrict that a little more, that he can only see some attributes of the users. Let's say for example:
id -> yes
firstname -> yes
lastname -> yes
address -> no!
Now I tried t change the query to something like:
var userHasProfessions = xy.GetUserProfessions();
var users = sessionService.GetDefaultSession()
.QueryOver<User>()
.SelectList(list => list
.Select(a => a.id)
.Select(a => a.firstname)
.Select(a => a.lastname))
.Where(a => userProfessions.Contains(a.Profession.Id))
.ToList();
Now my question... Is there a way to, for example, make a new List with these attributes and then loop through it? Something like that:
List<string> attributes = new List<string>(){"id", "firstname", "lastname"}
var userHasProfessions = xy.GetUserProfessions();
var users = sessionService.GetDefaultSession()
.QueryOver<User>()
.SelectList(
//loop through attributes
)
.Where(a => userProfessions.Contains(a.Profession.Id))
.ToList();
Thanks in advance :-)
EDIT
To make my question a little bit more clear. I wanna have the attributes that the user is allowed to see, dynmically changeable from a List<string> outside the query.
How can a achieve that?
You could do
.Where(a => userProfessions.Contains(a.Profession.Id))
.Select(c => new User {
id = c.id,
firstname = c.firstname,
lastname = c.lastname,
address c.address
}).ToList();
I am assuming list is type of User. Other wise you could use anonymous return i.d. c => new { id="id",... }

Group by some columns depending on values in Entity Framework

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 :)

removing items from a list based on two properties

I have the a list called orderList of type Order which has two properties. The list looks something like below.
Id Status
123 Good
878 Good
432 Good
123 Void
What I would like to do is to remove any orders that have a Status which is void and any Good orders which have the same Id as a void order. So the result would give me,
Id Status
878 Good
432 Good
What is the best way to do this? Is it just getting a list of void orders using linq and then looping through this new list to remove Good orders which share the same Id?
You can group and filter later, and flatten the groups at the end:
var result= list.GroupBy(e=>e.Id)
.Where(g=>g.Any(r=>r.Status=="Good") && g.All(r=>r.Status!="Void"))
.SelectMany(g=>g);
So you need to group first by your ID, after that specified your data and create new Field Remove which should be true if you have any Status Void in your group status collection. After that take objects only where the Remove is false and in the end create your RootOrder.
var result = list.GroupBy(x => x.ID)
.Select(x => new { ID = x.Key, Status = x.FirstOrDefault().Status, Remove = x.Any(y => y.Status == "Void") })
.Where(g => g.Remove == false)
.Select(r => new RootOrder { ID = r.ID, Status = r.Status }).ToList();
Full code example: dotNetFiddle
One way would be grouping, filtering and flattening the remaining groups - If you want a more 'literal' query you can try the following:
First filter out all of the non-Good Orders using Where:
orderList = orderList.Where(x => x.Status == Status.Good)
Then filter out all of the remaining Orders for which there are non-Good Orders in orderList containing the same Id using Where and Any:
.Where(x => !orderList
.Any(y => y.Status == Status.Void && y.Id == x.Id)
Finally, use the ToList() to take the returned IEnumerable as a List:
orderList = orderList
.Where(x => x.Status == Status.Good)
.Where(x => !orderList
.Any(y => y.Status == Status.Void && y.Id == x.Id)
.ToList();

Categories

Resources