I've this C# code to query my MongoDB collection:
var query = myCollection.FindAll().AsQueryable();
if (!string.IsNullOrWhiteSpace(username))
query = query.Where(
x => x.User.FullName.IndexOf(username, StringComparison.OrdinalIgnoreCase) >= 0);
if (!string.IsNullOrWhiteSpace(productName))
query = query.Where(
x => x.Product.ProductName.IndexOf(productName, StringComparison.OrdinalIgnoreCase) >= 0);
query = query.Take(pageSize).Skip(pageSize*(pageNumber-1));
var itemCount=query.Count();
var result = query.ToList();
Due to low performance now I want to use a full-text search. I created text index for User.FullName and Product.ProductName and I started to write code like this:
var textSearchCommand = new CommandDocument
{
{ "text", myCollection.Name },
{ "search", username }
};
var commandResult = _database.RunCommand(textSearchCommand);
var result = commandResult.Response;
Now I'm stuck; How to specify the property name in the above syntax example? Is this the right way to do that?
A text index points to the document as a whole, not to the individual field where the match occurs. That means a text-search is always performed on all fields which are part of the text-index. You can not selectively only search for matches in one field.
But what you can do is further filter the result-set of the $text-operator with additional operators. You could, for example, use an additional $regex-operator to check if the string you searched for occurs in the field where you want it to be.
Related
Is there a way to replace the value of one result with another after doing a linq statement?
is that in my linq statement in ** Status **, there are some that come to me with this result "PendingForApprover" but I want to replace it with "Pending for approver", I would like that after obtaining the result, you can use a find and based on that replace the values, before in my model I had it with a data annotation with a display name but for the reason that that result I am going to transform it into JSON and then it will be brought by server side that method is not working
y share my sentence LINQ
var result = db.document.Select(d => new DocumentViewModel
{
DocumentId = d.DocumentId,
Name = w.name
ReceivedLogs = d.Logs
Status = w.Status.toString(),
.Where(l => l.Status == Status.Received)
.Select(l => new LogViewModel
{
CurrentApprover = l.User,
NameApprover = l.User.FullName
}).FirstOrDefault()
}).ToList();
thanks
Have you tried with Switch expressions it is a pretty clear solution and you can integrate it in your LINQ query.
Status = w.Status switch
{
Status.StatusOne => "Status One",
Status.PendingForApprover => "Pending for approver",
_ => "Unknown"
}
I have a LINQ statement that I need to do a "contains" with, but also need some sort of loop.
The format of the data is as follows:
x.Product_Name = "product[x], product[y], product[z]"
As user selects multiple items from a list to search on.
I need to find anything within Product_Name that was selected from the user.
var names = JsonConvert.DeserializeObject<IEnumerable<string>>
(criteria.value).ToArray();
This line gets the items a user selected from the list and stores them in an array.
query = query.Where(x => names.contains(x.Product_Name))
Doesn't work because Product_Name is a flattened out version of products, so I can't do this.
What I need is something like the following:
foreach (string s in names)
{
projectsQuery = projectsQuery.Where(x => x.Product_Name.Contains(s));
}
But when the SQL is created for the above, it uses an AND conditional instead of an OR conditional. I need to find any instances where string s is contained within the Product_Name.
You can achieve it by creating Expression tree manually. Although it is kind of hard to manage code.
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var xParameter = Expression.Parameter(typeof(x), "x");
var searchexpression = new stack<expression>();
foreach (string s in names)
{
var containsmethodexp = expression.call(Expression.Property(xParameter, "Product_Name", containsMethod, expression.constant(s));
if (searchexpression.count == 0)
{
searchexpression.push(containsmethodexp);
}
else
{
searchexpression.push(expression.orelse(containsmethodexp, searchexpression.pop()));
}
}
var finalResult = projectsQuery.Where(Expression.Lambda<Func<x, bool>>(searchexpression.pop(), new ParameterExpression[] { xParameter }));
here x is your Entity Name
My users should be able to configure a filter to get a result from the database.
I decided to use an expression tree, to be flexible with the query. But I have problems with the expression tree for filtering by ReferenceCode.
Entities:
PumpStartUp
Duration (TimeSpan)
DutDbId (Guid/FK)
TimeStamp (DateTime)
DUT (DeviceUnderTest)
DeviceUnderTest
AdminId (string/unique)
DbId (Guid/PK)
ReferenceCode (string)
StartUps (List)
Here is a part of the filter in equivalent linq. But linq I can't use, because I don't know how many ReferenceCodes will be defined by the user.:
//-------------------reference code filter---------------------------
var query = context.PumpStartUps.Where((p => p.DUT.ReferenceCode == "HKR566" ||
p.DUT.ReferenceCode == "HSH967" ||
.
.
.));
startUps = query.ToList();
For filtering by DeviceUnderTest part of the filter, my solution is:
// --------------------duts filter-----------------------------------
Expression dutsExpression = null;
Expression psuExpression = Expression.Parameter(typeof(PumpStartUp), "psu");
Expression psuDutIdExpression = Expression.Property(psuExpression, "DutDbId");
foreach (var dut in filter.Duts)
{
DeviceUnderTest deviceUnderTest = context.DevicesUnderTest.Where(d => d.AdminId == dut.Id).Single();
Expression dutIdExpression = Expression.Constant(deviceUnderTest.DbId);
Expression dutExpression = Expression.Equal(pumpStartUpDutIdExpression, dutIdExpression);
if (dutsExpression == null)
{
dutsExpression = dutExpression;
}
else
{
dutsExpression = Expression.Or(dutsExpression, dutExpression);
}
}
How can I filter by ReferenceCode in that manner:
Use this:
var dutExpression = Expression.Property(psuExpression, "DUT");
var referenceCodeExp = = Expression.Property(dutExpression, "ReferenceCode ");
var constExpr = Expression.Constant("HKR566");
var eqExp = Expression.Equal(referenceCodeExp , constExpr);
dutsExpression = Expression.Or(dutsExpression, eqExp);
If you have a limited amount of codes, you can always say
var query = context.PumpStartUps.Where(p => codes.Contains(p.DUT.ReferenceCode))
This works up until about 2000 parameters. If you need more, then you should probably send the codes somehow to a temp table (or rather a function returning a table, since ef does not support temp tables), and join on that since constructing an expression with more than 2000 ors is not gonna perform well.
I have a simple custom table with a search/filter field. I leave the implementation of the search up to each use of the table.
So let's say I have users in my table and I want to search for them. I want to search both in users firstname, lastname and also any role they are in.
This would probably do the trick
searchString = searchString.ToLower();
query = query.Where(
x =>
x.FirstName.ToLower().Contains(searchString)
||
x.LastName.ToLower().Contains(searchString)
||
x.Roles.Any(
role =>
role.Name.ToLower().Contains(searchString)
)
);
But now I want to search/filter on multiple words. First I get an array of all separate words.
var searchStrings = searchString.ToLower().Split(null);
I tried the following but it does not fulfill my requirements listed further down as it returns any user where any word is matched in any field. I need that all words are matched (but possibly in different fields). Se below for more details.
query = query.Where(
x =>
searchStrings.Any(word => x.FirstName.ToLower().Contains(word))
||
searchStrings.Any(word => x.LastName.ToLower().Contains(word))
//snipped away roles search for brevity
);
First let me produce some data
Users (data)
Billy-James Carter is admin and manager
James Carter is manager
Billy Carter has no role
Cases
If my search string is "billy car" I want Billy-James and Billy returned but not James Carter (so all words must match but not on same field).
If my search string is "bil jam" or even "bil jam car" I only want Billy-James returned as he is the only one matching all terms/words. So in this the words bil and jam were both found in the FirstName field while the car term was found in the LastName field. Only getting the "car" part correct is not enough and James is not returned.
If I search for "car man" Billy-James and James are both managers (man) and named Carter and should show up. should I search for "car man admi" then only Billy-James should show up.
I am happy to abandon my current approach if better is suggested.
I cannot think of a way to wrap what you're looking for up into a single LINQ statement. There may be a way, but I know with EF the options are more limited than LINQ on an object collection. With that said, why not grab a result set from the database with the first word in the split, then filter the resulting collection further?
var searchWords = searchString.ToLower().split(' ');
var results = dataBase.Where(i => i.FirstName.ToLower().Contains(searchWords[0])
|| i.LastName.ToLower().Contains(searchWords[0])
|| i.Role.ToLower().Contains(searchWords[0]));
if(searchWords.Length > 1) {
for(int x = 1; x < searchWords.Length; x++) {
results = results.Where(i => i.FirstName.ToLower().Contains(searchWords[x])
|| i.LastName.ToLower().Contains(searchWords[x])
|| i.Role.ToLower().Contains(searchWords[x]));
}
}
Your final content of the results collection will be what you're looking for.
Disclaimer: I didn't have a setup at the ready to test this, so there may be something like a .ToList() needed to make this work, but it's basically functional.
Update: More information about EF and deferred execution, and string collection search
Given we have the schema:
Employee:
FirstName - String
Last Name - String
Roles - One to Many
Role:
Name - String
The following will build a query for everything you want to find
var searchTerms = SearchString.ToLower().Split(null);
var term = searchTerms[0];
var results = from e in entities.Employees
where (e.FirstName.Contains(term)
|| e.LastName.Contains(term)
|| e.Roles.Select(r => r.Name).Any(n => n.Contains(term)))
select e;
if (searchTerms.Length > 1)
{
for (int i = 1; i < searchTerms.Length; i++)
{
var tempTerm = searchTerms[i];
results = from e in results
where (e.FirstName.Contains(tempTerm)
|| e.LastName.Contains(tempTerm)
|| e.Roles.Select(r => r.Name).Any(n => n.Contains(tempTerm)))
select e;
}
}
At this point the query still has not been executed. As you filter the result set in the loop, this is actually adding additional AND clauses to the search criteria. The query doesn't execute until you run a command that does something with the result set like ToList(), iterating over the collection, etc. Put a break point after everything that builds the query and take a look at it. LINQ to SQL is both interesting and powerful.
More on deferred execution
The one thing which needs explanation is the variable tempTerm. We need a variable which is scoped within the loop so that we don't end up with one value for all the parameters in the query referencing the variable term.
I simplified it a bit
//we want to search/filter
if (!string.IsNullOrEmpty(request.SearchText))
{
var searchTerms = request.SearchText.ToLower().Split(null);
foreach (var term in searchTerms)
{
string tmpTerm = term;
query = query.Where(
x =>
x.Name.ToLower().Contains(tmpTerm)
);
}
}
I build a much bigger query where searching is just a part, starting like this
var query = _context.RentSpaces.Where(x => x.Property.PropertyId == request.PropertyId).AsQueryable();
above search only uses one field but should work just fine with more complex fields. like in my user example.
I usually take the apporach to sort of queue the queries. They are all executed in one step at the database if you look with the diagnostic tools:
IQueryable<YourEntity> entityQuery = context.YourEntity.AsQueryable();
foreach (string term in serchTerms)
{
entityQuery = entityQuery.Where(a => a.YourProperty.Contains(term));
}
I've figured out how to do conditional queries with linq to sql and I've also figured out how to OR where clauses. Unfortunately I can't figure out how to do both at once. I can do a conditional where clause something like:
var ResultsFromProfiles = from AllPeeps in SearchDC.aspnet_Users
select AllPeeps;
if (SearchFirstNameBox.Checked)
{
ResultsFromProfiles = ResultsFromProfiles.Where(p => p.tblUserProfile.FirstName.Contains(SearchTerm));
}
if (SearchLastNameBox.Checked)
{
ResultsFromProfiles = ResultsFromProfiles.Where(p => p.tblUserProfile.LastName.Contains(SearchTerm));
}
This will get me any profiles where the first name AND the last name contain the search term.
Or I could do:
var ResultsFromProfiles = from p in SearchDC.aspnet_Users
where p.tblUserProfile.LastName.Contains(SearchTerm) ||
p.tblUserProfile.FirstName.Contains(SearchTerm)
select p;
This would get me any profiles where the first name OR the last name contains the search term.
I have a bunch of checkboxes where the user can specify which fields they want to search for teh search term, so I want to be able to build a query that will conditionally add them as in the first code snippet above, but add them as an OR so they work like the second snippet. That way it will search for any matches anywhere in the specified fields.
Any tips?
Yeah, use PredicateBuilder. It lets you build up dynamic queries with And or Or semantics. It's free and stable- I use it all over the place.
One way to do this is to manipulate the LINQ expression tree for your query. In your case, you'd need to build a lambda expression and substitute it in for your call to Where. Here is a working example based on a list, but the code to manipulate the expression tree is the same regardless of the query provider.
List<User> Users = new List<User>();
Users.Add(new User() { FirstName = "John", LastName = "Smith" });
Users.Add(new User() { FirstName = "Jane", LastName = "Smith" });
string Query = "John";
var Queryable = Users.AsQueryable();
var Results = (from u in Queryable
select u);
//initial method call... the lambda u => false is a place-holder that is about to be replaced
MethodCallExpression WhereExpression = (MethodCallExpression)Results.Where(u => false).Expression;
//define search options
Expression<Func<User, string, bool>> FilterLastName = (u, query) => u.LastName.Contains(query);
Expression<Func<User, string, bool>> FilterFirstName = (u, query) => u.FirstName.Contains(query);
//build a lambda based on selected search options... tie the u parameter to UserParameter and the query parameter to our Query constant
ParameterExpression UserParameter = Expression.Parameter(typeof(User), "u");
Expression Predicate = Expression.Constant(false); //for simplicity, since we're or-ing, we'll start off with false || ...
//if (condition for filtering by last name)
{
Predicate = Expression.Or(Predicate, Expression.Invoke(FilterLastName, UserParameter, Expression.Constant(Query)));
}
//if (condition for filtering by first name)
{
Predicate = Expression.Or(Predicate, Expression.Invoke(FilterFirstName, UserParameter, Expression.Constant(Query)));
}
//final method call... lambda u => false is the second parameter, and is replaced with a new lambda based on the predicate we just constructed
WhereExpression = Expression.Call(WhereExpression.Object, WhereExpression.Method, WhereExpression.Arguments[0], Expression.Lambda(Predicate, UserParameter));
//get a new IQueryable for our new expression
Results = Results.Provider.CreateQuery<User>(WhereExpression);
//enumerate results as usual
foreach (User u in Results)
{
Console.WriteLine("{0} {1}", u.FirstName, u.LastName);
}
Working with expression trees can typically be simplified by using the visitor pattern, but I've omitted that so you could more clearly see the work that has to be done.
Here's a suggestion.
I haven't tried compiling and running it, and LinqToSQL is full of surprises, so no guarantees :-)
var ResultsFromProfiles = from AllPeeps in SearchDC.aspnet_Users select AllPeeps;
IEnumerable<AspNet_User> total = new AspNew_User[0];
if (SearchFirstNameBox.Checked)
{
total = total.Concat(ResultsFromProfiles.Where(p => p.tblUserProfile.FirstName.Contains(SearchTerm));}
}
if (SearchLastNameBox.Checked)
{
total = total.Concat(ResultsFromProfiles.Where(p => p.tblUserProfile.LastName.Contains(SearchTerm));
}
total = total.Distinct();