Troubles with Filtering - c#

My Filter is problematic. My goal is to have the datagrid on load show any with a created person where date > Today's day - 1 month.
I have some filters surname, forename. I want to Display any Person that had some activities within last 3 months. Thats found through this query
(ctx.Interactions.Where(z => z.Attendees.Where(w => w.Person_Id == x.Id).Any() && z.ActivityDate >= recent ).Any())
The issue I'm having is when surname or forename is filled, I want the query to ignore the created date prequisite and the Interaction Prerequisite.
Items.AddRange(ctx.People.
Where(x => (
((Surname.Length == 0) && (Forename.Length == 0)) ?
(x.Created > limit) : true &&
(((Surname.Length == 0) || x.Surname.StartsWith(Surname)) &&
((Forename.Length == 0) || x.Forename.StartsWith(Forename)) &&
(ctx.Interactions.Where(z => z.Attendees.Where(w => w.Person_Id == x.Id).Any() && z.ActivityDate >= recent ).Any())
One thing I did try was to move the Interaction query and had it with the x.created but that ruined the runtime. Currently it runs around 15seconds, with that change it takes about 2 mins. Any tips or suggestions would be great.
recent is today's date - 3 months

You can split the filtering expression:
var filteredPeople = ctx.People; // here var should be replaced with IEnumerable<T> where T is the type of items in People
if (string.IsNullOrEmpty(Surname) && string.IsNullOrEmpty(Forename))
filteredPeople = filteredPeople.Where(x => x.Created > limit);
else
{
if (!string.IsNullOrEmpty(Surname))
filteredPeople = filteredPeople.Where(x => x.Surname.StartsWith(Surname));
if (!string.IsNullOrEmpty(Forename))
filteredPeople = filteredPeople.Where(x => x.Forename.StartsWith(Forename));
filteredPeople = filteredPeople.Where(x => ctx.Interactions.Where(z => z.ActivityDate >= recent)
.Any(z => z.Attendees.Any(w => w.Person_id == p.Id)));
}
Items.AddRange(filteredPeople);

I think your issue may be in the use of your conditional operator. The conditional operator takes the ? and : clauses completely independently. So what you're currently telling it is,
If Surname and Forename are length 0
Then return ALL where x.Created > limit
Else
Then apply all other filters.
I've reworked the query a bit. This should improve performance because it will be retrieving fewer (and more accurate) results.
Items.AddRange(ctx.People.
Where(x => (
((Surname.Length == 0 && Forename.Length == 0) ? x.Created > limit : true)
&&
(
(Surname.Length == 0 || x.Surname.StartsWith(Surname))
&& (Forename.Length == 0 || x.Forename.StartsWith(Forename))
&& (ctx.Interactions.Any(z => z.Attendees.Any(w => w.Person_Id == x.Id) && z.ActivityDate >= recent))
)
Here's another version that doesn't hit the ctx twice, and instead traverses from Person > Attendee > Interaction instead of backwards. This might affect the performance as well:
Items.AddRange(ctx.People.
Where(x => (
((Surname.Length == 0 && Forename.Length == 0) ? x.Created > limit : true)
&&
(
(Surname.Length == 0 || x.Surname.StartsWith(Surname))
&& (Forename.Length == 0 || x.Forename.StartsWith(Forename))
&& x.Attendees.Any(attendee => attendee.Interactions.Any(interaction => interaction.ActivityDate >= recent))
)

Related

EntityFremework: could a select before a where optimize this?

I'm trying to gain performance on this query, and I'd like to know if calling a Select() before the Where() I could have some improvement:
public async Task<List<PostValues>> GetValuesToTheDashboard(DataFilter filter, CancellationToken cancellationToken) {
long startTimestanp = Helpers.UnixTimeNow(filter.StartDate);
long endTimestanp = Helpers.UnixTimeNow(filter.EndDate);
return await
_context.CatchDetails.Where(
x => x.Monitoring.Client.Id == filter.CustomerId && x.Data.published >= startTimestanp
&& x.Data.published <= endTimestanp
&& ((filter.Sentiment == Sentiments.ALL) || x.Sentiment_enum == filter.Sentiment)
&& (filter.MonitoringId == 0 || x.Monitoring.id == filter.MonitoringId)
&& (filter.KeywordId == 0 || x.Keyword.Id == filter.KeywordId)
&& (filter.MotiveId == 0 || x.Motive.Id == filter.MotiveId)
&& (filter.SocialNetwork.Count == 0 || filter.SocialNetwork.Any(s => x.Data.social_media == s))
&& (filter.Busca == "" || x.Data.content_snippet.Contains(filter.Busca))
&& (filter.Gender.Count == 0 || filter.Gender.Any(g => x.Data.extra_author_attributes.gender_enum == g)))
.Select(s => new PostValues() {
CatchDetailsId=s.Id,
Monitoring=s.Monitoring.name,
Keyword=s.Keyword.Text,
Motive=s.Motive.Name,
Sentiment=s.Sentiment_enum,
Gender=s.Data.extra_author_attributes.gender_enum,
SocialMedia=s.Data.social_media,
Country=s.Data.extra_author_attributes.world_data.country_code,
State=s.Data.extra_author_attributes.world_data.region,
Published=s.Data.published
}).ToListAsync(cancellationToken);
}
There might be a way how to improve performance, but it won't be with switching Select and Where (as Chetan mentioned in the comment).
You could build the query in a sequence and based on the filter get a simpler query in the end. This would go like this:
var query = _context.CatchDetails.Where(
x => x.Monitoring.Client.Id == filter.CustomerId && x.Data.published >= startTimestanp
&& x.Data.published <= endTimestanp);
if (filter.Sentiment != Sentiments.ALL)
{
query = query.Where(x => x.Sentiment_enum == filter.Sentiment);
}
if (filter.MonitoringId != 0)
{
query = query.Where(x => x.Monitoring.id == filter.MonitoringId);
}
[...]
return await
query.Select(s => new PostValues() {
[...]
}).ToListAsync(cancellationToken);
Do not forget, the variable query is already on memory of the application when the SQL returns data. If there is many results it could throw memory exception.
I suggest that you limit the range of date on that search.

Improve performance in linq query - trying to change the query from subquery to join

I am doing the below linq query which is costing me a lot and this query is in a loop which I can not avoid and I have to do it in C# which also I can not avoid. I have lot of logic above the linq query and after the query. I wanted to check if I can change anything on the query to improve the performance at least a little bit.
lstDataTable.Where(i => i.Field<int>("ALLL_Snapshot_ID") == 20 &&
i.Field<int>("ALLL_Analysis_Segment_Group_Column_ID") == 5 &&
i.Field<DateTime>("OriginationDate") > startingSnapshotDate &&
i.Field<DateTime>("OriginationDate") <= endingSnapshotDate &&
snapshotDataWithDate.Select(j => j.Field<string>
("MaturityDateBorrowerIdNoteNumberKey")).Contains(i.Field<string>
("MaturityDateBorrowerIdNoteNumberKey")) &&
snapshotDataWithDate.Select(j => j.Field<string>
("OriginationDateBorrowerIdNoteNumberKey")).Contains(i.Field<string>
("OriginationDateBorrowerIdNoteNumberKey")))
.Select(i => i.Field<Decimal>("BalanceOutstanding") + i.Field<Decimal>
("UndisbursedCommitmentAvailability")).Sum();
where lstDataTable and snapshotDataWithDate are IEnumerable of DataRow.
I tried above query using join but it is not joining properly. The difference between the two results is way high. Below is the query I tried using join
(from p in lstDataTable
join t in snapshotDataWithDate on p.Field<string>
("MaturityDateBorrowerIdNoteNumberKey") equals t.Field<string>
("MaturityDateBorrowerIdNoteNumberKey") &&
p.Field<string>("OriginationDateBorrowerIdNoteNumberKey") equals
t.Field<string>("OriginationDateBorrowerIdNoteNumberKey")
where p.Field<int>("ALLL_Analysis_Segment_Group_Column_ID") ==
SegmentGroupCECLSurvivalRateObj.ALLL_Segment_Group_Column_ID &&
p.Field<DateTime>("OriginationDate") > startingSnapshotDate &&
p.Field<DateTime>("OriginationDate") <= endingSnapshotDate
select p.Field<Decimal>("BalanceOutstanding") + p.Field<Decimal>
("UndisbursedCommitmentAvailability")).Sum();
Try this query, I have changed some expressions in where clause.
lstDataTable.Where(i => i.Field<int>("ALLL_Snapshot_ID") == 20 &&
i.Field<int>("ALLL_Analysis_Segment_Group_Column_ID") == 5 &&
i.Field<DateTime>("OriginationDate") > startingSnapshotDate &&
i.Field<DateTime>("OriginationDate") <= endingSnapshotDate &&
snapshotDataWithDate.Any(j => j.Field<string>
("MaturityDateBorrowerIdNoteNumberKey") == i.Field<string>
("MaturityDateBorrowerIdNoteNumberKey")) &&
snapshotDataWithDate.Any(j => j.Field<string>
("OriginationDateBorrowerIdNoteNumberKey") == i.Field<string>
("OriginationDateBorrowerIdNoteNumberKey")))
.Select(i => i.Field<Decimal>("BalanceOutstanding") + i.Field<Decimal>
("UndisbursedCommitmentAvailability")).Sum();
Perhaps pulling out the Field accesses will provide a small amount of optimization?
var snapshotDataConvertedMDB = snapshotDataWithDate.Select(r => r.Field<string>("MaturityDateBorrowerIdNoteNumberKey")).ToList();
var snapshotDataConvertedODB = snapshotDataWithDate.Select(r => r.Field<string>("OriginationDateBorrowerIdNoteNumberKey")).ToList();
var ans = lstDataTable
.Select(r => new {
ALLL_Snapshot_ID = r.Field<int>("ALLL_Snapshot_ID"),
ALLL_Analysis_Segment_Group_Column_ID = r.Field<int>("ALLL_Analysis_Segment_Group_Column_ID"),
OriginationDate = r.Field<DateTime>("OriginationDate"),
MaturityDateBorrowerIdNoteNumberKey = r.Field<string>("MaturityDateBorrowerIdNoteNumberKey"),
OriginationDateBorrowerIdNoteNumberKey = r.Field<string>("OriginationDateBorrowerIdNoteNumberKey"),
BalanceOutstanding = r.Field<Decimal>("BalanceOutstanding"),
UndisbursedCommitmentAvailability = r.Field<Decimal>("UndisbursedCommitmentAvailability")
})
.Where(i => i.ALLL_Snapshot_ID == 20 &&
i.ALLL_Analysis_Segment_Group_Column_ID == 5 &&
i.OriginationDate > startingSnapshotDate &&
i.OriginationDate <= endingSnapshotDate &&
snapshotDataConvertedMDB.Contains(i.MaturityDateBorrowerIdNoteNumberKey) &&
snapshotDataConvertedODB.Contains(i.OriginationDateBorrowerIdNoteNumberKey))
.Select(i => i.BalanceOutstanding + i.UndisbursedCommitmentAvailability)
.Sum();

How to accelerate C#/Linq query? [i don't need to get data, i need to get condition]

public int being = 0;
public void Insert(Currency current, int number)
{
being = db.Currency.Where(x => x.ForDate == current.ForDate)
.Where(x => x.TitleId == current.TitleId)
.Where(x => x.Value == current.Value).Count(x=>x.Id > 0);
if (being == 0)
{
db.Currency.AddOrUpdate(current);
}
}
it's my code works so slowly, because of getting date but it is not necessary, i don't know other way.
maybe something like :
db.Currency.Find().Value.Equals(current.Value).where...where...
I think your main problem is the .Count(x => x.Id > 0), which forces the evaluation of all the conditions before and actually get the total number.
If you can, replace it with Any. In that way, it just has to get one row at most:
bool isBeing = db.Currency
.Where(x => x.ForDate == current.ForDate
&& x.TitleId == current.TitleId
&& x.Value == current.Value
&& x.Id > 0
)
.Any();
You can do all your conditions in just one where, and also you can skip having a bool variable to check your conditions
if(db.Currency.Where(x => x.ForDate == current.ForDate
&& x.TitleId == current.TitleId && x.Value == current.Value && x.Id > 0).Any())
{
db.Currency.AddOrUpdate(current);
}

Compare complex subquery between two values

I have repository with students and I want to get some students who have grades count in between 0 and 2.
This is my code:
_unitOfWork.Repository<Student>().Get(o => o.OrganizationId == organizationId
&& o.Grades.Where( o1 => o1.LastVersion
&& o1.Type == 5
&& o1.Value == 1).Count() > 0
&& o.Grades.Where( o1 => o1.LastVersion
&& o1.Type == 5
&& o1.Value == 1).Count() <= 2 );
This code is working, but my question is how to change this query with less code.
Is there any way to replace Count with some variable and not use it two times in query?
something like this?
var values = Enumerable.Range(1, 2);
_unitOfWork.Repository<Student>().Get(o => o.OrganizationId == organizationId
&& values.Contains(
o.Grades.Where( o1 => o1.LastVersion
&& o1.Type == 5
&& o1.Value == 1).Count()
));

Linq2Sql strange behaviour

in my DB, I have e.g. 13 orders.
The code below returns all of them, if the OrderID = 0 and CustomerName = "lorem".
If I comment the line (OrderID == 0) ?.... it works fine. What's wrong ?
var result = (from x in db.Order
where
(OrderID == 0) ? x.OrderID > 0 : x.OrderID == OrderID
&&
(string.IsNullOrEmpty(CustomerName)) ?
!string.IsNullOrEmpty(CustomerName)
:
x.User.Name.Contains(CustomerName)
select x)
.ToList();
I think you can not define conditional condition inside LINQ query in this way, what you can do is, for example:
var result = (from x in db.Order where
((OrderID == 0 && x.OrderID > 0) ||
(OrderID != 0 && x.OrderID == OrderID))
&&
(string.IsNullOrEmpty(CustomerName)) ?
!string.IsNullOrEmpty(CustomerName)
:
x.User.Name.Contains(CustomerName)....

Categories