Below is a rough and simplified code example of what I am trying to do and it is not working as expected. I am using Linq to Entity in case that matters.
Basically in the first loop with the DateClause's ends up returning nothing if I have two date clauses, works great with one date clause. In the TextClause's loop it seems to ignore every text clause after the first one.
My expectation is that the clauses would be ANDed together, so that if I do a text clause and then do another they should be added and I would get a more focused results, especially with using the Contains method.
Expectations for the dates are that I would expect to be able to do a date range with this and but if there are two dates it always returns nothing, even though I know there are records with the right dates in the range.
I am sure I am doing something wrong or plain stupid, but I can't see it.
enum Operators
{
Contains,
DoesNotContain,
GreaterThan,
LessThan
}
public void DoSomething(List<DateClauses> dateClauses, List<TextClauses> textClauses)
{
var query = from t in context.Table
where t.Enabled = true
select new
{
title = t.title
date = t.date
}
foreach (DateClause clause in dateClauses)
{
switch (clause.Operator)
{
case Operator.GreaterThan:
query = query.Where(l => l.date > clause.Date);
break;
case Operator.LessThan
query = query.Where(l => l.date < clause.Date);
break;
}
}
foreach (TextClause clause in textClauses)
{
switch (clause.Operator)
{
case Operator.Contains:
query = query.Where(l => l.title.Contains(clause.Text));
break;
case Operator.DoesNotContain
query = query.Where(l => !l.title.Contains(clause.Text));
break;
}
}
}
EDIT: Updated example code to show use of enum in the process. When I extrapolated the use of the enum in to some of the very nice solutions (that otherwise would work with a bool) cause an exception shown in a comment from me in Joel's response.
I want to say I like what I have seen in responses so far, and I have learned a few new Linq tricks, I apologize for the example as I didn't think bool vs enum would matter much with Linq. I hope the changes will help find a solution that can work for me. Thanks again for the great responses to date.
Just a wild guess, but could it be that changing your loops to the following has different results?
foreach (DateClause clause in dateClauses)
{
var capturedClause = clause;
switch (clause.Operator)
{
case Operator.GreaterThan:
query = query.Where(l => l.date > capturedClause.Date);
break;
case Operator.LessThan
query = query.Where(l => l.date < capturedClause.Date);
break;
}
}
You're creating Where criteria capturing the loop variable, not the value of the loop variable for each iteration. So in the end, each Where will be the same, using the value for the last iteration of the loop, as the query is executed after the last iteration. By introducing a temp variable you'll capture the variable for each iteration.
See also Eric Lippert's blog about this subject.
var query = context.Table
.Where(t => t.Enabled
&& textClauses.Where(c => c.Contains).All(c => t.title.Contains(c.Text) )
&& textClauses.Where(c => c.DoesNotContain).All(c => !t.title.Contains(c.Text) )
&& dateClauses.Where(c => c.GreaterThan).All(c => t.date > c.Date) )
&& dateClauses.Where(c => c.LesserThan).All(c => t.date < c.Date) )
).Select(t => new {
title = t.title
date = t.date
});
The main thing here is that each of your current foreach loops can be refactored to use .All() instead. Or, rather, each if condition inside the loop can be. If you really want to you can still break out each of the where's:
var query = context.Table.Where(t => t.Enabled).Select(t => new {
title = t.title
date = t.date
});
query = query.Where(t => textClauses.Where(c => c.Contains).All(c => t.title.Contains(c.Text) );
query = query.Where(t => textClauses.Where(c => c.DoesNotContain).All(c => !t.title.Contains(c.Text) );
query = query.Where(t => dateClauses.Where(c => c.GreaterThan).All(c => t.date > c.Date) );
query = query.Where(t => dateClauses.Where(c => c.LesserThan).All(c => t.date < c.Date) );
I've done this before with no issues- perhaps you should try moving the .Select to the end (after the Wheres are done). Also, I use PredicateBuilder heavily for added flexibility with dynamic query stuff- right now you're getting "and" semantics for your appended wheres, where I'd guess you want "or" semantics for at least some of them. PredicateBuilder lets you do that easily (and it's free!).
Related
I am relatively new to Entity Framework 6.0 and I have come across a situation where I want to execute a query in my C# app that would be similar to this SQL Query:
select * from periods where id in (select distinct periodid from ratedetails where rateid = 3)
Is it actually possible to execute a query like this in EF or would I need to break it into smaller steps?
Assuming that you have in your Context class:
DbSet<Period> Periods...
DbSet<RateDetail> RateDetails...
You could use some Linq like this:
var distincts = dbContext.RateDetails
.Where(i => i.rateId == 3)
.Select(i => i.PeriodId)
.Distinct();
var result = dbContext.Periods
.Where(i => i.Id)
.Any(j => distincts.Contains(j.Id));
Edit: Depending on your entities, you will probably need a custom Comparer for Distinct(). You can find a tutorial here, and also here
or use some more Linq magic to split the results.
Yes, this can be done but you should really provide a better example for your query. You are already providing a bad starting point there. Lets use this one:
SELECT value1, value2, commonValue
FROM table1
WHERE EXISTS (
SELECT 1
FROM table2
WHERE table1.commonValue = table2.commonValue
// include some more filters here on table2
)
First, its almost always better to use EXISTS instead of IN.
Now to turn this into a Lambda would be something like this, again you provided no objects or object graph so I will just make something up.
DbContext myContext = this.getContext();
var myResults = myContext.DbSet<Type1>().Where(x => myContext.DbSet<Type2>().Any(y => y.commonValue == x.commonValue)).Select(x => x);
EDIT - updated after you provided the new sql statement
Using your example objects this would produce the best result. Again, this is more efficient than a Contains which translates to an IN clause.
Sql you really want:
SELECT *
FROM periods
WHERE EXISTS (SELECT 1 FROM ratedetails WHERE rateid = 3 AND periods.id = ratedetails.periodid)
The Lamda statement you are after
DbContext myContext = this.getContext();
var myResults = myContext.DbSet<Periods>()
.Where(x => myContext.DbSet<RateDetails>().Any(y => y.periodid == x.id && y.rateid == 3))
.Select(x => x);
Here is a good starting point for learning about lamda's and how to use them.
Lambda Expressions (C# Programming Guide).
this is your second where clause in your query
var priodidList=ratedetails.where(x=>x.rateid ==3).DistinctBy(x=>x.rateid);
now for first part of query
var selected = periods.Where(p => p.id
.Any(a => priodidList.Contains(a.periodid ))
.ToList();
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);
}
How to set multiple values of a list object, i am doing following but fails.
objFreecusatomization
.AllCustomizationButtonList
.Where(p => p.CategoryID == btnObj.CategoryID && p.IsSelected == true && p.ID == btnObj.ID)
.ToList()
.ForEach(x => x.BtnColor = Color.Red.ToString(), );
In the end after comma i want to set another value.
What should be the expression, although i have only one record relevant.
Well personally I wouldn't write the code that way anyway - but you can just use a statement lambda:
A statement lambda resembles an expression lambda except that the statement(s) is enclosed in braces
The body of a statement lambda can consist of any number of statements; however, in practice there are typically no more than two or three.
So the ForEach call would look like this:
.ForEach(x => {
x.BtnColor = Color.Red.ToString();
x.OtherColor = Color.Blue.ToString();
});
I would write a foreach loop instead though:
var itemsToChange = objFreecusatomization.AllCustomizationButtonList
.Where(p => p.CategoryID == btnObj.CategoryID
&& p.IsSelected
&& p.ID == btnObj.ID);
foreach (var item in itemsToChange)
{
item.BtnColor = Color.Red.ToString();
item.OtherColor = Color.Blue.ToString();
}
(You can inline the query into the foreach statement itself, but personally I find the above approach using a separate local variable clearer.)
I have an entity framework object called batch, this object has a 1 to many relationship to items.
so 1 batch has many items. and each item has many issues.
I want to filter the for batch items that have a certain issue code (x.code == issueNo).
I have written the following but Im getting this error:
items = batch.Select(b => b.Items
.Where(i => i.ItemOrganisations
.Select(o => o
.Issues.Select(x => x.Code == issueNo))));
Error 1:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<System.Collections.Generic.IEnumerable<bool>>' to 'bool'
Error 2:
Cannot convert lambda expression to delegate type 'System.Func<Ebiquity.Reputation.Neptune.Model.Item,bool>' because some of the return types in the block are not implicitly convertible to the delegate return type
Select extension method needs a lambda expression that returns a boolean, but the inner o.Issues.Select returns an IEnumerable of boolean to the outer Select(o => o which result in the exception you're getting.
Try using Any instead which verifies that at least one element verifies the condition:
items = batch.Select(
b => b.Items.Where(
i => i.ItemOrganisations.Any(
o => o.Issues.Any(x => x.Code == issueNo)
)
)
);
If I understand correctly, you're trying to select through multiple layers of enumerables. In those cases you need SelectMany which flattens out the layers, not Select. LINQ's syntax sugar is made specifically to make SelectMany easier to reason about:
var items = from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item;
The compiler translates that into something like this:
var items = batch.Items
.SelectMany(item => item.ItemOrganizations, (item, org) => new {item, org})
.SelectMany(#t => #t.org.Issues, (#t, issue) => new {#t, issue})
.Where(#t => #t.issue.Code == issueNo)
.Select(#t => #t.#t.item);
You can always wrap this in a Distinct if you need to avoid duplicate items:
var items = (from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item).Distinct();
It's hard to tell what you're trying to do based on your code but I think you're looking for something like this;
var issue = batch.Select(b => b.Items).Select(i => i.Issues).Where(x => x.Code == issueNo).Select(x => x).FirstOrDefault();
The above query will return the first issue where the Issues Code property is equal to issueNo. If no such issue exists it will return null.
One problem (the cause of your first error) in your query is that you're using select like it's a where clause at the end of your query. Select is used to project an argument, when you do Select(x => x.Code == issueNo) what you're doing is projecting x.Code to a bool, the value returned by that select is the result of x.Code == issueNo, it seems like you want that condition in a where clause and then you want to return the issue which satisfies it which is what my query is doing.
items = from b in batch.Include("Items")
where b.Items.Any(x=>x.Code==issueNo)
select b;
You're getting lost in lambdas. Your LINQ chains are all embedded in each other, making it harder to reason about. I'd recommend some helper functions here:
static bool HasIssueWithCode(this ItemOrganization org, int issueNo)
{
return org.Issues.Any(issue => issue.Code == issueNo);
}
static bool HasIssueWithCode(this Item items, int issueNo)
{
return items.ItemOrganizations.Any(org => org.HasIssueWithCode(issueNo));
}
Then your answer is simply and obviously
var items = batch.Items.Where(item => item.HasIssueWithCode(issueNo));
If you inline these functions, the result is the exact same as manji's (so give manji credit for the correct answer), but I think it's a bit easier to read.
I spent long time trying to fix this but I'm not getting anywhere, so i help someone helps me.
I have a search form where I wish to use the Like % operator in the textbox.
Here is a snippet of my code:
var data = mg.DatabaseTable.Where(m => m.UserName.StartsWith(TextBoxUserID.Text) &&
m.Content.Like(%TextBoxBarcode.Text&) &&
m.Action.StartsWith(DropDownListStatus.Text) &&
m.Site.Contains(TextBoxSite.Text));
I would like it to be possible to use "%%" in the m.Content(textbox) or however it's made.
I am aware the use of StartsWith, EndWith and Contains. I would like to make it possible to choose how to query the search by using "like %"
You can use:
m.Content.StartsWith(TextBoxBarcode.Text);
for
like 'search%'
or:
m.Content.EndsWith(TextBoxBarcode.Text);
for
like '%search'
or:
m.Content.Contains(TextBoxBarcode.Text);
for
like '%search%'
If you want the user to be able to choose the type of search then you'll need to have a switch and then three different queries. Either replicate the query or perform the basic query once and then filter the results of that depending on the switch:
var data = mg.DatabaseTable.Where(m => m.UserName.StartsWith(TextBoxUserID.Text) &&
m.Action.StartsWith(DropDownListStatus.Text) &&
m.Site.Contains(TextBoxSite.Text));
if (searchMode == StartsWith)
{
return data.Where(m => m.Content.StartsWith(TextBoxBarcode.Text);
}
else if (searchMode == EndsWith)
{
return data.Where(m => m.Content.EndsWith(TextBoxBarcode.Text);
}
else
{
return data.Where(m => m.Content.Contains(TextBoxBarcode.Text);
}