Searching any word using Linq - c#

In my winform app I have a form where user can search for product by typing any text in search field. For example the product description might be stored as "Adidas aftershave 100Ml" . But user can type any thing like 100 Ml Aftershave. So I would like to query using linq to get all records where description contains any of these word(100 Ml Aftershave).
So far I have done something like this:
List<string>txtList = txtSearchTerm.Text.Split(' ').ToList();
return dbContext.productRepo.Where(t => txtList.Any(b =>
t.ProductDescription.StartsWith(b)
|| t.ProductDescription.EndsWith(b)
|| t.ProductDescription.Contains(b)
)).Select(x => x.ProductDescription).ToList();
Any other way of achieving this in better way ,making it more quick or any other improvement.
Thanks

Well, you are testing if the description starts, ends and contains something. The first two are already redundant because Contains "contains" them:
var matchingDescriptions = dbContext.productRepo
.Where(x => txtList.Any(x.ProductDescription.Contains))
.Select(x => x.ProductDescription));
Another easy optimization(for Linq-To-Objects), I would order the words by length first:
var txtList = txtSearchTerm.Text.Split(' ').OrderBy(s => s.Length).ToList()

One thing you can straightaway improve is to remove StartsWith and EndsWith , Becuase you are already doing t.ProductDescription.Contains(b) .
List<string>txtList = txtSearchTerm.Text.Split(' ').ToList();
return dbContext.productRepo.Where(t => txtList.Any(b =>
t.ProductDescription.Contains(b)
)).Select(x => x.ProductDescription).ToList();

Related

Contains any word of string

I trying to get from Database any name or tag that match any of words from string.
Something like search.
Example:
Query: "Any query that matches"
I have first table with Name, and second with Tags for First Table.
I need something like this, not performance heavy. This will not work because Contains check whole string.
_db.Tag_Table.Where(t => t.First_Table.Name.Contains(query) ||
t.Tag_Table.Value.Contains(query))
.Select(s => s.First_Table).ToList();
Any reasonable solution for this.
I think I found solution, it returns correct results I think.
var queryList = query.Split(' ');
_db.Post.Where(f => queryList.Any(q => f.Title.Contains(q)) || queryList.Any(l => f.Tags.Any(t => t.Tag.Value.Contains(l))));
In every post, for every Query word I'm searching in Post Title or Every Tag Name in Tags Table. I'm returning result if I have Query word in title or in tag.
Thank you for your answers.
It sounds that #Zeeshan Adil 2nd answer is similar.
can you please try this and let me know if it worked in comments:
string[] terms = Query.Split(' ');
string[] Results;
string[] all = (from x in _db.Tag_Table
select x.Name).ToArray();
Results =(from z in all
from w in terms
where z.Contains(w)
select z).Distinct().ToArray();
Edit
also please try this:
string Query = "some value";
string[] terms = Query.Split(' ');
from x in _db.Tag_Table
where terms.Any(val => p.First_Table.Name.Contains(val))
select x;

LINQ Intersect on inner collection

I have a list of Stores (of type ObservableCollection<Store>) and the Store object has a property called Features ( of type List<Feature> ). and the Feature object has a Name property (of type string).
To recap, a list of Stores that has a list of Features
I have a second collection of DesiredFeatures (of type List<string> ).
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures. So far, I've only been able to come up with a query that gives me an OR result instead of AND.
Here's what that looks like:
var q = Stores.Where(s=> s.Features.Any(f=> DesiredFeatures.Contains(f.name)));
I know Intersect can help, and here's how I've used it:
var q = Stores.Where(s => s.Features.Intersect<Feature>(DesiredFeatures));
This is where I'm stuck, Intersect wants a Feature object, what I need to intersect is on the Feature.Name.
The goal is to end up with an ObservableCollection where each Store has all of the DesiredFeatures.
Thank you!
You've almost done what you need. A small refine would be to swap DesiredFeatures and s.Features.
var q = Stores.Where(s => DesiredFeatures.All(df => s.Features.Contains(df)));
It means take only those stores where desired features are all contained in features of the store.
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures.
In other words, each desired feature must have a matching store feature.
I don't see how Intersect can help in this case. The direct translation of the above criteria to LINQ is like this:
var q = Stores.Where(s =>
DesiredFeatures.All(df => s.Features.Any(f => f.Name == df))
);
A more efficient way could be to use a GroupJoin for performing the match:
var q = Stores.Where(s =>
DesiredFeatures.GroupJoin(s.Features,
df => df, sf => sf.Name, (df, sf) => sf.Any()
).All(match => match)
);
or Except to check for unmatched items:
var q = Stores.Where(s =>
!DesiredFeatures.Except(s.Features.Select(sf => sf.Name)).Any()
);
Going on your intersect idea, the only way I thought of making this work was by using Select to get the Store.Features (List<Feature>) as a list of Feature Names (List<string>) and intersect that with DesiredFeatures.
Updated Answer:
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures).Any());
or
var q = Stores.Where(s => DesiredFeatures.Intersect(s.Features.Select(f => f.Name)).Any());
Old Answer (if DesiredFeatures is a List<Feature>):
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures.Select(df => df.Name)).Any());
Two things you want your code to perform.
var q = Stores.Where(s=> s.Features.All(f=> DesiredFeatures.Contains(f.name)) &&
s.Features.Count() == DesiredFeatures.Count()); // Incude Distinct in the comparison if Features list is not unique
Ensure that every Feature is DesiredFeature
Store contains all Desired features.
Code above assumes uniqueness in Features collection as well as DesiredFeatures, modify code as stated in comment line if this is not right

RavenDB Query with unknown amount of arguments?

I'm trying to implement search functionality for an application and i'd like to make a query which can take an unkown amount of arguments/search terms.
for example, if i've got two arguments i'd like something like this.
using (IDocumentSession session = RavenDbConfig.RavenDBDocumentStore.OpenSession())
{
var searchresults = session.Query<Contact>()
.Where(x => x.Firstname.StartsWith("searchArgument1") || x.Firstname.StartsWith("searchArgument2"))
.ToList();
}
... and so on.
So is this possible? If not, how would you approach a problem where you don't know how many search terms the user wants to use to search for something?
You can append more where clauses to your query before enumerating it. I think you have to use the DocumentQuery<> to be able to do what you want (or maybe the Search() feature, not quite sure):
using (var session = _documentStore.OpenSession())
{
var query = session.Advanced.DocumentQuery<Contact>()
.WhereStartsWith(x => x.FirstName, "searchArgument1");
if(hasSearchArgument2)
query = query.WhereStartsWith(x => x.FirstName, "searchArgument2");
var contacts = query.ToList();
}
This will return all documents with FirstName that starts with either searchArgument1 or searchArgument2.
The Lucene query that gets executed in the above example looks like:
{FirstName:searchArgument1* FirstName:searchArgument2*}
Read more about DocumentQuery<> here: http://ravendb.net/docs/article-page/3.0/csharp/indexes/querying/query-vs-document-query
Also, read about Search as it might be more suited for your situation: http://ravendb.net/docs/article-page/3.0/csharp/indexes/querying/searching
Hope this helps!

Identify items in one list not in another of a different type

I need to identify items from one list that are not present in another list. The two lists are of different entities (ToDo and WorkshopItem). I consider a workshop item to be in the todo list if the Name is matched in any of the todo list items.
The following does what I'm after but find it awkward and hard to understand each time I revisit it. I use NHibernate QueryOver syntax to get the two lists and then a LINQ statement to filter down to just the Workshop items that meet the requirement (DateDue is in the next two weeks and the Name is not present in the list of ToDo items.
var allTodos = Session.QueryOver<ToDo>().List();
var twoWeeksTime = DateTime.Now.AddDays(14);
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime).List();
var matches = from wsi in workshopItemsDueSoon
where !(from todo in allTodos
select todo.TaskName)
.Contains(wsi.Name)
select wsi;
Ideally I'd like to have just one NHibernate query that returns a list of WorkshopItems that match my requirement.
I think I've managed to put together a Linq version of the answer put forward by #CSL and will mark that as the accepted answer as it put me in the direction of the following.
var twoWeeksTime = DateTime.Now.AddDays(14);
var subquery = NHibernate.Criterion.QueryOver.Of<ToDo>().Select(t => t.TaskName);
var matchingItems = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime &&
w.IsWorkshopItemInProgress == true)
.WithSubquery.WhereProperty(x => x.Name).NotIn(subquery)
.Future<WorkshopItem>();
It returns the results I'm expecting and doesn't rely on magic strings. I'm hesitant because I don't fully understand the WithSubquery (and whether inlining it would be a good thing). It seems to equate to
WHERE WorkshopItem.Name IS NOT IN (subquery)
Also I don't understand the Future instead of List. If anyone would shed some light on those that would help.
I am not 100% sure how to achieve what you need using LINQ so to give you an option I am just putting up an alternative solution using nHibernate Criteria (this will execute in one database hit):
// Create a query
ICriteria query = Session.CreateCriteria<WorkShopItem>("wsi");
// Restrict to items due within the next 14 days
query.Add(Restrictions.Le("DateDue", DateTime.Now.AddDays(14));
// Return all TaskNames from Todo's
DetachedCriteria allTodos = DetachedCriteria.For(typeof(Todo)).SetProjection(Projections.Property("TaskName"));
// Filter Work Shop Items for any that do not have a To-do item
query.Add(SubQueries.PropertyNotIn("Name", allTodos);
// Return results
var matchingItems = query.Future<WorkShopItem>().ToList()
I'd recommend
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime)
var allTodos = Session.QueryOver<ToDo>();
Instead of
var allTodos = Session.QueryOver<ToDo>().List();
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime).List();
So that the collection isn't iterated until you need it to be.
I've found that it's helpfull to use linq extension methods to make subqueries more readable and less awkward.
For example:
var matches = from wsi in workshopItemsDueSoon
where !allTodos.Select(it=>it.TaskName).Contains(wsi.Name)
select wsi
Personally, since the query is fairly simple, I'd prefer to do it like so:
var matches = workshopItemsDueSoon.Where(wsi => !allTodos.Select(it => it.TaskName).Contains(wsi.Name))
The latter seems less verbose to me.

Delegates in Linq

I have a list of entities that contains search terms, clicks etc.
The user should be able to compare the amount of clicks between the entities - i.e. all the entities that contains the word "free sample" in the beginning of the search and compare with all the entities that contains the phrase "arrest me" in the end of the phrase.
I can do this with multiple foreach and switch (because the user choose the query and the part of the string) but i understand that by using Linq i can use the "start with" and "end with" functions. could you please guide me through?
It sounds like you want a list of things that start with "free sample" and then another that ends with "arrest me". Assuming that's what you want, you'll do something like this.
// Assumes myStuff is where all of your data is right now
var startsWith = myStuff.Where(x => x.MyString.StartsWith("free sample"));
var endsWith = myStuff.Where(x => x.MyString.EndsWith("arrest me"));
// Now you can do whatever comparisons between the two lists you need
Of course, if you just want all of them that start with "free sample" and end with "arrest me" you can just do this
var hasBoth = myStuff.Where(x => x.MyString.StartsWith("free sample") && x.MyString.EndsWith("arrest me"))
LINQ Where() to return a set of entities which satisfies a condition:
var filtered = entities.Where(e =>
e.SearchPattern.StartsWith("Start")
&&
e.SearchPattern.EndsWith("End"));
LINQ Single()/SingleorDefault() to return a single entity which satisfies a condition, if more than one found - exception will be thrown.
var filtered = entities.Single(e =>
e.SearchPattern.StartsWith("Start")
&&
e.SearchPattern.EndsWith("End"));
LINQ First()/Last() to return first/last entity for the given condition...
var filtered = entities.First(e =>
e.SearchPattern.StartsWith("Start")
&&
e.SearchPattern.EndsWith("End"));
See Enumerable class methods for the full list
The functions you mention are just string functions. s.StartsWith("abc") and s.EndsWith("xyz"). In a LINQ query you can use them in a where clause.
var startIndexes = phrases
.Where(p => p.StartsWith("free sample")
.Select((p, i) => i);
var endIndexes = phrases
.Where(p => p.EndsWith("arrest me")
.Select((p, i) => i);
These two queries return the indexes of phrases that start, respectively end with a certain string. I use an overload of select that accepts the index as second parameter.

Categories