How to make lambda do one db call instead of severals - c#

Is this example bad practice because it make several database calls?
Is it any way I can make this to one DB call? Like use 'where' instead of 'firstOrDefault' and compare FlagDate with each date on the message in my messageList?
foreach (var message in messageList)
{
var dayFlag = db.DayFlags.FirstOrDefault(x =>
x.FlagDate == message.MessageDate);
}

If you want to retrieve only the dayFlags which a corresponding Date to the messageList, you need to extract the dates first in a separate list, then pass it to a Linq To Sql query.
Note that to retrieve only the first DayFlags of each Date, you need to group the flags by date.
var dates = messageList.Select(m => m.MessageDate).ToList();
var dayFlags = db.DayFlags.GroupBy(flag => flag.FlagDate)
.Where(group => dates.Contains(group.Key))
.Select(group => group.First());

This is a solution with linq:
var dates = messageList.Select(m => m.MessageDate).ToList();
var dayFlags = from df in db.DayFlags
where dates.Contains(df.FlagDate)
select df;

Related

align a list by date in ascending order using OrderBy

I have this query that fetches me some items that have a registered request, I created a class to store the item and request data in strings, and I need to sort them by the date of creation from the latest to the oldest.
var Items = await this.ItemsRepository.GetAllAsync(expression);
var report = new List<RegisteredOrders>();
foreach (var item in Items)
{
var request = await this.RequestRepository.GetOneAsync(x => !x.Deleted && x.Id == item.requestId);
report.Add (new RegisteredRequest
(
requestDate: request.CreatedAt.ToString("dd/MM/yyyy"),
requestNum: request.RequestCode,
itemDate: item.CreatedAt.ToString("dd/MM/yyyy"),
itemNum: item.ItemCode
));
}
var orderReport = report.OrderBy(x => x.requestDate).ThenBy(x => x.itemDate);
return orderReport.ToList();
tried to use OrderBy but it's not working, What's happening?
I have no way to change the variables within the RegisteredRequest class. So I had to convert the strings to date time so I could use OrderBy as #lidqy said and it worked.
var orderReport = report.OrderBy(x => Convert.ToDateTime(x.requestDate).Date).ThenBy(x => Convert.ToDateTime(x.itemDate).Date);
Do you have control over the RegisteredRequest Class? My approach would be to keep the property you assign to as DateTime and if you really want to be able to access it as a string then make another property that only has a get accessor that is the tostring of the datetime in the format you want.
You could then sort by the DateTime property that you set, but use the formatted date property in whatever report you're displaying.

How to dynamically add Where and Or statements to a Linq query [duplicate]

This question already has answers here:
Dynamic where clause (OR) in Linq to Entities
(2 answers)
Closed 3 years ago.
First time using c# and Linq. I have a string coming in through my route. I want to search a couple of different columns for the values in my string. Assuming I'm splitting each word on a space, foreach one of these items I want to dynamically add a .Where to my linq statement. I'm thinking I may need to dynamically add an .Or as well.
foreach (string q in query)
{
results = results.Where(u => u.Name.Contains(r));
results = results.Where(u => u.Text.Contains(r));
}
I'm used to JS where you could do something like results += results.Where(...) I'm not sure the appropriate way to structure this kind of thing using linq.
edit: here is the entire method for clarity
using (var context = new MessageContext())
{
string[] words = query.Split(" ");
var messages = (from m in context.Messages
join u in context.Users on m.UserId equals u.UserID
select new
{
m.Id,
m.Date,
m.Name,
m.Text,
m.UserId,
u.Image
});
foreach (string word in words)
{
messages = messages.Where(u => u.Name.Contains(word)).Union(messages.Where(u => u.Text.Contains(word)));
return messages.ToList();
}
Linq uses lazy evaluation (the results are not evaluated until you start to iterate over the results, or until you call a method like ToList()). As John pointed out each successive call is really just modifiying the search criteria. therefore in your example
results = results.Where(u => u.Name.Contains(r));
results = results.Where(u => u.Text.Contains(r));
is equivalent to
results = results.Where(u => u.Name.Contains(r)).Where(u => u.Text.Contains(r));
which implies a AND condition. If you want an OR condition you would need to us the Union operator.
results = results.Where(u => u.Name.Contains(r)).Union(results.Where(u => u.Text.Contains(r)));
The benefit of this lazy evaluation is that you can extract a base query and add on additional search criteria, thus simplifying your code.
I hope this helps.
foreach (string q in query)
{
results = results.Where(u => u.Name.Contains(r) || u.Text.Contains(r));
}
Or may be you need to elaborate your question a bit more

LINQ list exclude item with latest datetime

I have a list of objects, the object contains a datetime.
I want to use LINQ to exclude the object with the latest datetime.
I'm trying something like:
var excludedList = testdata.Except(testdata.Max(c => c.registeredAt).FirstOrDefault()).ToList();
But it does not work, what should the correct linq be?
You could try something like this:
var excludedList = testdata.OrderByDescending(i => i.registeredAt).Skip(1).ToList();
This will sort the list by date, then skip the first item.
Here's a solution that doesn't perform a whole sort just to extract a single maximum value, and preserves the order of the sequence. It does still depend on the maximum value being unique.
var maxRegisteredAt = testdata.Max(c => c.registeredAt);
var excludedList = testdata
.Where(c => c.registeredAt != maxRegisteredAt)
.ToList();
This is one possible approach if duplicate registeredAt datetimes aren't possible:
var maxObj = testdata.OrderByDescending(x => x?.registeredAt).First();
var excludedList = testdata.FindAll(x => x != maxObj);
This keeps the original order.

Linq with boolean function to relational db in Entity Framework

Probably a few things wrong with my code here but I'm mostly having a problem with the syntax. Entry is a model for use in Entries and contains a TimeStamp for each entry. Member is a model for people who are assigned entries and contains an fk for Entry. I want to sort my list of members based off of how many entries the member has within a given period (arbitrarily chose 30 days).
A. I'm not sure that the function I created works correctly, but this is aside from the main point because I haven't really dug into it yet.
B. I cannot figure out the syntax of the Linq statement or if it's even possible.
Function:
private bool TimeCompare(DateTime TimeStamp)
{
DateTime bound = DateTime.Today.AddDays(-30);
if (bound <= TimeStamp)
{
return true;
}
return false;
}
Member list:
public PartialViewResult List()
{
var query = repository.Members.OrderByDescending(p => p.Entry.Count).Where(TimeCompare(p => p.Entry.Select(e => e.TimeStamp));
//return PartialView(repository.Members);
return PartialView(query);
}
the var query is my problem here and I can't seem to find a way to incorporate a boolean function into a .where statement in a linq.
EDIT
To summarize I am simply trying to query all entries timestamped within the past 30 days.
I also have to emphasize the relational/fk part as that appears to be forcing the Timestamp to be IEnumerable of System.Datetime instead of simple System.Datetime.
This errors with "Cannot implicitly convert timestamp to bool" on the E.TimeStamp:
var query = repository.Members.Where(p => p.Entry.First(e => e.TimeStamp) <= past30).OrderByDescending(p => p.Entry.Count);
This errors with Operator '<=' cannot be applied to operands of type 'System.Collections.Generic.IEnumerable' and 'System.DateTime'
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp) <= past30).OrderByDescending(p => p.Entry.Count);
EDIT2
Syntactically correct but not semantically:
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp).FirstOrDefault() <= timeComparison).OrderByDescending(p => p.Entry.Count);
The desired result is to pull all members and then sort by the number of entries they have, this pulls members with entries and then orders by the number of entries they have. Essentially the .where should somehow be nested inside of the .count.
EDIT3
Syntactically correct but results in a runtime error (Exception Details: System.ArgumentException: DbSortClause expressions must have a type that is order comparable.
Parameter name: key):
var query = repository.Members.OrderByDescending(p => p.Entry.Where(e => e.TimeStamp <= timeComparison));
EDIT4
Closer (as this line compiles) but it doesn't seem to be having any effect on the object. Regardless of how many entries I add for a user it doesn't change the sort order as desired (or at all).
var timeComparison = DateTime.Today.AddDays(-30).Day;
var query = repository.Members.OrderByDescending(p => p.Entry.Select(e => e.TimeStamp.Day <= timeComparison).FirstOrDefault());
A bit of research dictates that Linq to Entities (IE: This section)
...var query = repository.Members.OrderByDescending(...
tends to really not like it if you use your own functions, since it will try to map to a SQL variant.
Try something along the lines of this, and see if it helps:
var query = repository.Members.AsEnumerable().Where(TimeCompare(p => p.Entry.Select(e => e.TimeStamp).OrderByDescending(p => p.Entry.Count));
Edit: I should just read what you are trying to do. You want it to grab only the ones within the last X number of days, correct? I believe the following should work, but I would need to test when I get to my home computer...
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30);
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp).FirstOrDefault() <= timeComparison).OrderByDescending(p => p.Entry.Count));
//return PartialView(repository.Members);
return PartialView(query);
}
Edit2: This may be a lack of understanding from your code, but is e the same type as p? If so, you should be able to just reference the timestamp like so:
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30);
var query = repository.Members.Where(p => p.TimeStamp <= timeComparison).OrderByDescending(p => p.Entry.Count));
//return PartialView(repository.Members);
return PartialView(query);
}
Edit3: In Edit3, I see what you are trying to do now (I believe). You're close, but OrderByDescending would need to go on the end. Try this:
var query = repository.Members
.Select(p => p.Entry.Where(e => e.TimeStamp <= timeComparison))
.OrderByDescending(p => p.Entry.Count);
Thanks for all the help Dylan but here is the final answer:
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30).Day;
var query = repository.Members
.OrderBy(m => m.Entry.Where(e => e.TimeStamp.Day <= timeComparison).Count());
return PartialView(query);
}

Multiple Include and Where Clauses Linq

I have a database where I'm wanting to return a list of Clients.
These clients have a list of FamilyNames.
I started with this
var query = DbContext.Clients.Include(c => c.FamilyNames).ToList() //returns all clients, including their FamilyNames...Great.
But I want somebody to be able to search for a FamilyName, ifany results are returned, then show the clients to the user.
so I did this...
var query = DbContext.Clients.Include(c => c.FamilyNames.Where(fn => fn.familyName == textEnteredByUser)).ToList();
I tried...
var query = DbContext.Clients.Include(c => c.FamilyNames.Any(fn => fn.familyName == textEnteredByUser)).ToList();
and...
var query = DbContext.FamilyNames.Include(c => c.Clients).where(fn => fn.familyname == textEnteredByUser.Select(c => c.Clients)).ToList();
What I would like to know (obviously!) is how I could get this to work, but I would like it if at all possible to be done in one query to the database. Even if somebody can point me in the correct direction.
Kind regards
In Linq to Entities you can navigate on properties and they will be transformed to join statements.
This will return a list of clients.
var query = DbContext.Clients.Where(c => c.FamilyNames.Any(fn => fn == textEnteredByUser)).ToList();
If you want to include all their family names with eager loading, this should work:
var query = DbContext.Clients.Where(c => c.FamilyNames.Any(fn => fn == textEnteredByUser)).Include(c => c.FamilyNames).ToList();
Here is some reference about loading related entities if something doesn't work as expected.
You can use 'Projection', basically you select just the fields you want from any level into a new object, possibly anonymous.
var query = DbContext.Clients
.Where(c => c.FamilyNames.Any(fn => fn == textEnteredByUser))
// only calls that can be converted to SQL safely here
.Select(c => new {
ClientName = c.Name,
FamilyNames = c.FamilyNames
})
// force the query to be materialized so we can safely do other transforms
.ToList()
// convert the anon class to what we need
.Select(anon => new ClientViewModel() {
ClientName = anon.ClientName,
// convert IEnumerable<string> to List<string>
FamilyNames = anon.FamilyNames.ToList()
});
That creates an anonymous class with just those two properties, then forces the query to run, then performs a 2nd projection into a ViewModel class.
Usually I would be selecting into a ViewModel for passing to the UI, limiting it to just the bare minimum number of fields that the UI needs. Your needs may vary.

Categories