Multi nested FirstOrDefault - c#

I have the following model:
public class Stredisko
{
public string Name { get; set; }
public ObservableCollection<SubStredisko> Pracoviska { get; set; }
public ObservableCollection<SubStredisko> Dohodari { get; set; }
}
public class SubStredisko
{
public string Name { get; set; }
public string Code { get; set; }
public VyplatnePasky VyplatnaPaska { get; set; }
public MzdoveNaklady MzdoveNaklady { get; set; }
public Poistne Poistne { get; set; }
}
I am now trying to run a super simple LINQ query which should return first element which matches the following condition:
var sStredisko = Strediska.FirstOrDefault(
n => n.Pracoviska.FirstOrDefault(x=>x.Name == "somename"));
I Run this condition against: ObservableCollection<Stredisko> Strediska
However for unknown reason (to me) it gives me the following error:
Cannot implicitly convert type 'SubStredisko' to 'bool'.
What am I doing wrong?

You're looking for Enumerable.Any:
var sStredisko = Strediska.FirstOrDefault(
n => n.Pracoviska.Any(x => x.Name == "somename"));
FirstOrDefault will yield the first element that matches the predicate. You want to match against the first element and yield a bool indicating the match has been found, which is what Any does.

Partially related to OP's question, this is what I was searching when I found this question:
I needed to actually get the nested object instead of the top one.
So re-using OP's model it would look like this:
var sSubStredisko = Strediska
.Select(n => n.Pracoviska.FirstOrDefault(x => x.Name == "somename"))
.FirstOrDefault(s => s != null);
Because of LINQ's deferred execution this won't loop twice but will just return the first nested element it will find (or none).

Related

How to update a property of an complexObj. inside a Document inside a List<complexObj>? MongoDB C# net.Driver

how to update one property of a document in MongoDb with C# net.Driver.
My data model in C# looks like this:
public class MyMediListsWrapper
{
[BsonId]
public Guid Id { get; set; }
public DateTime Datum { get; set; }
public bool IsActiv { get; set; }
public List<Medi> MediLsts { get; set; }
}
public class Medi
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Typ { get; set; }
public string Vmax { get; set; }
}
Now I want to update only one property of one Obj. (Vmax='Test') of the nested List MediLsts.
I wand to filter the collection MyMediListsWrapper by SingleOrDefault IsActiv=true and then I want to filter 'in this Document' the Medi-Obj. from the nested List inside by Guid='XcPwoEaB8Ey7XR1AEB+fLQ=='
and then update Vmax='Test'.
I have a little bit of code, but it doesn't achieve what I want. I have trouble to do the filter first for IsActiv and then filter inside of that Document again for a different property. And I don't know how to write the code for the update later (if found). Thx for help.
My Code:
var db = client.GetDatabase(MySecret.MyMongoDbName);
var myMediListsWrapper = db.GetCollection<MyMediListsWrapper>(MySecret.MyWrapperColName);
var filter = Builders<MyMediListsWrapper>.Filter;
var filterColAndFilterLstObj = filter.And(
filter.Eq(x => x.IsActiv, true),
filter.ElemMatch(x => x.MediLsts, lst => lst.Id == mySearchGuid));
//Just a Test -- I want to UPDATE a property NOT find a entry
//and this is WRONG because it give me a obj of the outer class not the Medi-Obj.
var result = await myMediListsWrapper.Find(filterColAndFilterLstObj).SingleOrDefaultAsync(); ```
the following update command should do the trick:
await collection.UpdateOneAsync(
filter: w => w.IsActiv && w.MediLsts.Any(m => m.Id == mySearchGuid),
update: Builders<MyMediListsWrapper>.Update.Set(m => m.MediLsts[-1].Vmax, "TEST"));
the thing to note here is the [-1] array/list index above.
the driver translates that to the following $set operation:
$set: { "MediLsts.$.Vmax" : "TEST" }
-1 index position is something special the mongo driver uses to denominate $ which is the first positional operator.
i.e. update the first array element that qualifies for the $elemMatch condition of the filter.
.MediLsts.Any(...) is the LINQ equivalent of filter.ElemMatch(...)

LINQ Filter Nth Level Nested list

I have the following hierarchy in my project :
Activity
Task
Step
Responses
This means an activity has many tasks, which in turn has many steps , a step has many responses.
Here are my POCO classes:
public class Activity
{
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public virtual ICollection<Step> Steps{ get; set; }
}
public class Step
{
public virtual int DisplayOrder { get; set; }
public virtual ICollection<Response> Responses{ get; set; }
}
public class Response
{
public virtual int UserId { get; set; }
public virtual int ResponseText{ get; set; }
}
Now, I need to return a List<Activity> which is sorted by ActivityId and has Steps ordered by DisplayOrder and also a Responses which only belong to a given UserId.
Here's what I have tried:
Activities.ToList()
.ForEach((activity) => activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.ForEach((step) => step.Responses = step.Responses.Where(r => r.UserId == RequestorUserId)))
));
Which is giving me an error:
Cannot implicitly convert type 'void' to ICollection<Step>
The ForEach extension method doesn't return anything and you are trying to set the result to task.Steps. You can achieve the desired result by using the Select method to alter the objects in line
Activities.ToList()
.ForEach((activity) => activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.Select((step) =>
{
step.Responses = step.Responses.Where(r => r.UserId == 1).ToList();
return step;
}).ToList()));
Also It might be worth changing your types if possible to IEnumerable rather than ICollection as you then won't need all of the ToList() calls.
If you need to assign the result then you'll need to replace the first ForEach as well - try something like:
var a = Activities.ToList()
.Select((activity) =>
{
activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.Select((step) =>
{
step.Responses = step.Responses.Where(r => r.UserId == 1).ToList();
return step;
}).ToList());
return activity;
});

Filter list 1 from list 2

Im having a problem here with performance.
I got to List contains (50k items)
and List contains (120k items)
WholeSaleEntry is
public class WholeSaleEntry
{
public string SKU { get; set; }
public string Stock { get; set; }
public string Price { get; set; }
public string EAN { get; set; }
}
and ProductList
public class ProductList
{
public string SKU { get; set; }
public string Price { get; set; }
public string FinalPrice { get; set; }
public string AlternateID { get; set; }
}
I need to filter WholeSaleEntry By its EAN and Its SKU in case that their EAN or SKU is in the ProductList.AlternateID
I wrote this code which works but the performance is really slow
List<WholeSaleEntry> filterWholeSale(List<WholeSaleEntry> wholeSaleEntry, List<ProductList> productList)
{
List<WholeSaleEntry> list = new List<WholeSaleEntry>();
foreach (WholeSaleEntry item in wholeSaleEntry)
{
try
{
string productSku = item.SKU;
string productEan = item.EAN;
var filteredCollection = productList.Where(itemx => (itemx.AlternateID == productEan) || (itemx.AlternateID == productSku)).ToList();
if (filteredCollection.Count > 0)
{
list.Add(item);
}
}
catch (Exception)
{
}
}
return list;
}
Is there any better filtering system or something that can filter it in bulk?
The use of .Where(...).ToList() will find every matching item, and in the end you only need to know if there is a matching item. That can be fixed using Any(...) which stops as soon as a match is found, like this:
var hasAny = productList.Any(itemx => itemx.AlternateID == productEan || itemx.AlternateID == productSku);
if (hasAny)
{
list.Add(item);
}
Update: the algorithm can be simplified to this. First get the unique alternate IDs using a HashSet, which stores duplicate items just once, and which is crazy fast for lookup. Then get all WholeSale items that match with that.
There will not be many faster strategies, and the code is small and I think easy to understand.
var uniqueAlternateIDs = new HashSet<string>(productList.Select(w => w.AlternateID));
var list = wholeSaleEntry
.Where(w => uniqueAlternateIDs.Contains(w.SKU)
|| uniqueAlternateIDs.Contains(w.EAN))
.ToList();
return list;
A quick test revealed that for 50k + 50k items, using HashSet<string> took 28ms to get the answer. Using a List<string> filled using .Distinct().ToList() took 48 seconds, with all other code the same.
If you do not want a specific method and avoid the list just do this.
var filteredWholeSale = wholeSaleEntry.Where(x => productList.Any(itemx => itemx.AlternateID == x.EAN || itemx.AlternateID == x.SKU));

Where condition inside lambda expression c#

I have a entity like
public class Program
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
}
and
public class EMetrics
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
public List<Program> Programs { get; set; }
}
I have repository method like,
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(x => x.ID)))
.ToList();
return metrics;
}
[The above code throwing build error]
Here only where I am facing problem to get the EMetrics based on the program Ids array params.
I want list Emetrics which are associated with the program.
You're incorrectly accessing the same input parameter in your LINQ. It should be refactored by changing your inner Select to use a different parameter:
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(y => y.ID)))
.ToList();
return metrics;
}
So you want to check if all elements of one collection are present in the other. In LINQ that can be done with combination of Except and Any:
var metrics = EntitySet
.Where(x => x.Programs.Select(p => p.ID).Except(programIds).Any())
.ToList();
Fyi - your current code is failing because Array.Contains expects a single item, an int in this case, while you are giving it a whole enumerable

LINQ Lambda: Getting Last item (where == ) of sub collection

I have the following (which I've used in CodeFirst):
public class MyForm
{
public int MyFormId { get; set; }
//Many Form Properties
public virtual ICollection<AuditItem> AuditItems { get; set; }
}
public class AuditItem
{
public int AuditItemId { get; set; }
public int MyFormId { get; set; }
public IAppUser User { get; set; }
public string ActionByUserStr { get; set; }
public string ActionByUserDisplayName { get; set; }
public DateTime DateOfAction { get; set; }
public string TypeOfAction { get; set; }
public int Step { get; set; }
public virtual MyForm MyForm { get; set; }
}
The AuditItem tracks many actions carried out on MyForm
I'm presenting an index page showing a table where the AuditItem's last Step = 1.
I do an orderby DateOfAction (because the Step can get setback to 0, so I wouldn't want it to equal 1 unless it is also the last AuditItem Record for that MyForm), then Select the last record's step, then query on that. I want to query as early as I can so that I'm not pulling back unrequired MyForms records.
This is the query I have:
var myFormsAtStep1 =
context.MyForms.Include("AuditItems").Where(
mf => mf.AuditItems.OrderBy(ai => ai.DateOfAction).Last().Step == 1);
It gives this error:
LINQ to Entities does not recognize the method 'MyForms.Models.AuditItem Last[AuditItem](System.Collections.Generic.IEnumerable`1[MyForms.Models.AuditItem])' method, and this method cannot be translated into a store expression.
Have a look at the list of Supported and Unsupported LINQ Methods.
Last() is not supported, but First() is, so you can just go on and reverse your ordering and use First() instead of Last().
mf => mf.AuditItems.OrderByDescending(ai => ai.DateOfAction).First().Step == 1);
You simply sort in descending order and get the first item.
var myFormsAtStep1 =
context.MyForms.Include("AuditItems").Where(
mf => mf.AuditItems.OrderByDescending(
ai => ai.DateOfAction).First().Step == 1);
I would also use the query syntax, which would be more readable, as well as the strongly-typed Include (import System.Data.Entity):
from mf in context.MyForms.Include(x => x.AuditItems)
let items = from ai in mf.AuditItems
orderby ai.DateOfAction descending
select ai;
where items.First().Step == 1
select mf

Categories