How to combine these two LINQ statements? - c#

How can I combine these two LINQ statements into one?
var Priority = repository.GetMany<Letter>(l => l.UserID == currentUser.ID)
.Select(l => l.Priority)
.FirstOrDefault();
var User = repository.GetMany<Letter>(l => l.Priority > Priority)
.Select(l => l.User)
.FirstOrDefault();
I need to get Priority of the currentUser and then get the next user that has the next Priority. For example, if the Priority of currentUser is 1, I need to get the user with Priority == 2.
Example:
letter=new letter {
User=Mark, Priority=1
User=Raha, Priority=2
User=Searah, Priority=3
}
when currentUser is Mark with Priority=1,i need to get user with Priority=2,in this sample Raha!

Assuming that your GetMany are indeed just Where calls, and therefore they returns an IQueryable, the following should do the trick I think:
var users = repository
.GetMany<Letter>(l => l.Priority > (
repository.GetMany<Letter>(
l2 => l2.UserID = currentUser.ID
)
.Select(l2 => l2.Priority)
.First()
)
.OrderBy(l => l.Priority)
.Select(l => l.User)
.Take(1);
Your first query is basically just inserted in the second query.

Related

Cannot access groupBy data Linq C#

Inside method I have a list that contains grouped data:
var listofData = _context.DBONE.where(x => x.Id==3 && x.Status!=0)
.GroupBy(x => new { x.Name, x.Class })
.Select(q => new { Result = q.ToList() }).ToList();
if (methodParam == 10)
{
data = listofData.Where(x => FunctionCheck(---CANNOT ACCESS THE FIELDS FROM GROUP DATA TO PASS AS PARAMETERS---) == 10).ToList();
}
And this is the function that will receive 2 parameter from the grouped data:
private int FunctionCheck(int id, string name)
{...}
But, I cannot access the desired field inside 'listofData'. I can access only in case the listofData is not using groupBy().
If I understand what you are trying to do correctly, you are able to access your data but you are actually creating a "list of a list"
Watch my example, I think I have reproduced your scenario here:
As you can see, I then have a "result" which contains a list of users where Id == 3. The problem is that you create a new anonymous object with a props that is a list. So if you try the last thing you see in my image above, I think you will be able to access your rows.
The reason is that after your GroupBy call, the result is of a grouping type - every item of your list is an Enumerable of the original item, so you would have to operate on that grouping in a following manner:
// Groups such that all items in that group pass your check
listofData
.Where(group => group.All(item => FunctionCheck(item.Id, item.Name) == 10))
.ToList();
// Groups where at least one item matches
listofData
.Where(group => group.Any(item => FunctionCheck(item.Id, item.Name) == 10))
.ToList();
The desired outcome is not really clear from the question but this is the step you are likely missing.
Another approach which might be potentially useful is pre-filter the colleciton of items before grouping them:
var listOfGroupedDatas = _context.DBONE
.Where(x => x.Id ==3 && x.Status != 0 && FunctionCheck(item.Id, item.Name) == 10)
.GroupBy(x => new { x.Name, x.Class })
.ToList();
// This will result in a list of groupings in which all items pass your check
I think you want to call SelectMany to project into one dimensional array.
var listofData = _context.DBONE.where(x => x.Id==3 && x.Status!=0)
.GroupBy(x => new { x.Name, x.Class })
.SelectMany(q => q.ToList()).ToList();

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

Using two Linq query in a single method

As shown in the below code, the API will hit the database two times to perform two Linq Query. Can't I perform the action which I shown below by hitting the database only once?
var IsMailIdAlreadyExist = _Context.UserProfile.Any(e => e.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = _Context.UserProfile.Any(x => x.Username == myModelUserProfile.Username);
In order to make one request to database you could first filter for only relevant values and then check again for specific values in the query result:
var selection = _Context.UserProfile
.Where(e => e.Email == myModelUserProfile.Email || e.Username == myModelUserProfile.Username)
.ToList();
var IsMailIdAlreadyExist = selection.Any(x => x.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = selection.Any(x => x.Username == myModelUserProfile.Username);
The .ToList() call here will execute the query on database once and return relevant values
Start with
var matches = _Context
.UserProfile
.Where(e => e.Email == myModelUserProfile.Email)
.Select(e => false)
.Take(1)
.Concat(
_Context
.UserProfile
.Where(x => x.Username == myModelUserProfile.Username)
.Select(e => true)
.Take(1)
).ToList();
This gets enough information to distinguish between the four possibilities (no match, email match, username match, both match) with a single query that doesn't return more than two rows at most, and doesn't retrieve unused information. Hence about as small as such a query can be.
With this done:
bool isMailIdAlreadyExist = matches.Any(m => !m);
bool isUserNameAlreadyExist = matches.LastOrDefault();
It's possible with a little hack, which is grouping by a constant:
var presenceData = _Context.UserProfile.GroupBy(x => 0)
.Select(g => new
{
IsMailIdAlreadyExist = g.Any(x => x.Email == myModelUserProfile.Email),
IsUserNameAlreadyExist = g.Any(x => x.Username == myModelUserProfile.Username),
}).First();
The grouping gives you access to 1 group containing all UserProfiles that you can access as often as you want in one query.
Not that I would recommend it just like that. The code is not self-explanatory and to me it seems a premature optimization.
You can do it all in one line, using ValueTuple and LINQ's .Aggregate() method:
(IsMailIdAlreadyExist, IsUserNameAlreadyExist) = _context.UserProfile.Aggregate((Email:false, Username:false), (n, o) => (n.Email || (o.Email == myModelUserProfile.Email ? true : false), n.Username || (o.Username == myModelUserProfile.Username ? true : false)));

LINQ equal instead of Contains

I need to use equal instead of Contains.
I have an array of codes called selectedDeviceTypeIDs i assume it has two codes {1,2}
I need get result from the query if Devices ids are exactly {1,2} so i have replace selectedDeviceTypeIDs.Contains with selectedDeviceTypeIDs.equal or something like that ...
m => m.Devices.Any(w => selectedDeviceTypeIDs.Contains(w.DeviceTypeID)
if (DeviceTypeIDs != null)
{
Guid[] selectedDeviceTypeIDs = DeviceTypeIDs.Split(',').Select(Guid.Parse).ToArray();
query = query.Where(j => j.HospitalDepartments.Any(jj => jj.Units.Any(m => m.Devices.Any(w => selectedDeviceTypeIDs.Contains(w.DeviceTypeID)))));
}
Use !.Except().Any() to make sure m.Devices doesn't contains any DeviceTypeID not present in selectedDeviceTypeIDs
query = query.Where(j => j.HospitalDepartments.Any(jj => jj.Units
.Where(m => !m.Devices.Select(w => w.DeviceTypeID).Except(selectedDeviceTypeIDs).Any())));
Option 1:
If you care about the Order of the items, use SequenceEqual extension method. This will return false, even if the collection has the items but in different order
m => m.Devices.Any(w => selectedDeviceTypeIDs.SequenceEqual(w.DeviceTypeID)
Option 2:
If you don't care about the order , use All extension method. This will return true, if the items in both collections are same irrespective of the order.
m => m.Devices.Any(w => selectedDeviceTypeIDs.All(w.DeviceTypeID.Contains)
You need to check if the selectedDeviceTypeIDs contains every device, and that every device contains selectedDeviceTypeIDs. You could use this:
query = query
.Where(j =>
j.HospitalDepartments.Any(jj =>
jj.Units.Any(m =>
m.Devices.All(
w => selectedDeviceTypeIDs.Contains(w.DeviceTypeID))
&&
selectedDeviceTypeIDs.All(
g => m.Devices.Select(d => d.DeviceTypeID).Contains(g))
)
)
);

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