ASP.NET MVC 3 - Search with multiple terms - c#

I have a method where by I'm able to search in a database for a specific customer(s). At the moment it only takes 1 term, but i'd like to be able to search with multiple terms (for example the customer's account number and their name). Below is my method:
public List<AXCustomer> allCustomers(string id)
{
string[] searchstring = id.Split(' ');
List<AXCustomer> customer = new List<AXCustomer>();
// if 3 terms are entered
if (searchstring.Length > 2)
{
}
// if 2 terms are entered
else if (searchstring.Length > 1)
{
}
// revert back to default search
else
{
customer = context.AXCustomers.Where(x => x.ACCOUNTNUM.Contains(id) ||
x.NAME.Contains(id) || x.ZIPCODE.Contains(id)).ToList();
}
return customer;
}
As you can see, i've decided to split each term entered (I assume each term will be seperated by a space) but I'm not sure how my LINQ query should be for terms longer than one. Any help would be appreciated

Since you don't know what will be entered or how long it will be, I would suggest doing the following:
public List<AXCustomer> allCustomers(string id)
{
string[] searchstring = id.Split(' ');
List<List<AXCustomer>> customerlists = new List<List<AXCustomer>>();
foreach (string word in searchstring)
{
customerlists.Add(context.AXCustomers.Where(x => x.ACCOUNTNUM.Contains(word) || x.NAME.Contains(word) || x.ZIPCODE.Contains(word)).ToList());
}
//Then you just need to see if you want ANY matches or COMPLETE matches.
//Throw your results together in a List<AXCustomer> and return it.
return mycombinedlist;
}
Any matches = throw all lists together, then take the distinct ones.
Complete matches = you'll have to check for items which occur in all customerlists.

It will work fine. I am using a similar type of query in my project and it seems to work great. Following is the code snippet
PagedList.IPagedList<Product> PagedProducts = dbStore.Products.Where(p => p.Name.Contains(query) || p.MetaKeywords.Contains(query)).ToList().ToPagedList(pageIndex, PageSize);
BTW, its running on a live server too.

You can dynamically attach as many conditions as you wish, in the following manner:
customer = context.AXCustomers.Where(x => x.ACCOUNTNUM.Contains(id));
customer = customer.Where(Condition 2);
customer = customer.Where(Condition 3);
And so on. You have full control over the criteria : just make sure that it resolves to a sequel query.

Related

If it possible accelerate From-Where-Select method?

I have two var of code:
first:
struct pair_fiodat {string fio; string dat;}
List<pair_fiodat> list_fiodat = new List<pair_fiodat>();
// list filled 200.000 records, omitted.
foreach(string fname in XML_files)
{
// get FullName and Birthday from file. Omitted.
var usersLookUp = list_fiodat.ToLookup(u => u.fio, u => u.dat); // create map
var dates = usersLookUp[FullName];
if (dates.Count() > 0)
{
foreach (var dt in dates)
{
if (dt == BirthDate) return true;
}
}
}
and second:
struct pair_fiodat {string fio; string dat;}
List<pair_fiodat> list_fiodat = new List<pair_fiodat>();
// list filled 200.000 records, omitted.
foreach(string fname in XML_files)
{
// get FullName and Birthday from file. Omitted.
var members = from s in list_fiodat where s.fio == FullName & s.dat == Birthdate select s;
if (members.Count() > 0 return true;
}
They make the same job - searching user by name and birthday.
The first one work very quick.
The second is very slowly (10x-50x)
Tell me please if it possible accelerate the second one?
I mean may be the list need in special preparing?
I tried sorting: list_fiodat_sorted = list_fiodat.OrderBy(x => x.fio).ToList();, but...
I skip your first test and change Count() to Any() (count iterate all list while any stop when there are an element)
public bool Test1(List<pair_fiodat> list_fiodat)
{
foreach (string fname in XML_files)
{
var members = from s in list_fiodat
where s.fio == fname & s.dat == BirthDate
select s;
if (members.Any())
return true;
}
return false;
}
If you want optimize something, you must leave comfortable things that offer the language to you because usually this things are not free, they have a cost.
For example, for is faster than foreach. Is a bit more ugly, you need two sentences to get the variable, but is faster. If you iterate a very big collection, each iteration sum.
LINQ is very powerfull and it's wonder work with it, but has a cost. If you change it for another "for", you save time.
public bool Test2(List<pair_fiodat> list_fiodat)
{
for (int i = 0; i < XML_files.Count; i++)
{
string fname = XML_files[i];
for (int j = 0; j < list_fiodat.Count; j++)
{
var s = list_fiodat[j];
if (s.fio == fname & s.dat == BirthDate)
{
return true;
}
}
}
return false;
}
With normal collections there aren't difference and usually you use foeach, LINQ... but in extreme cases, you must go to low level.
In your first test, ToLookup is the key. It takes a long time. Think about this: you are iterating all your list, creating and filling the map. It's bad in any case but think about the case in which the item you are looking for is at the start of the list: you only need a few iterations to found it but you spend time in each of the items of your list creating the map. Only in the worst case, the time is similar and always worse with the map creation due to the creation itself.
The map is interesting if you need, for example, all the items that match some condition, get a list instead found a ingle item. You spend time creating the map once, but you use the map many times and, in each time, you save time (map is "direct access" against the for that is "sequencial").

Lucene boosting not working

I'm indexing a document and setting the boost as follows:
document.SetBoost(5f);
because I want certain documents to appear before. For example, I want more recent news to show first.
When I do the search, like this:
var parser = new QueryParserEx(Version.LUCENE_29, "contents", analyzer);
parser.SetDefaultOperator(QueryParser.Operator.AND);
parser.SetMultiTermRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE);
Query query;
query = parser.Parse("text*");
The query gets parsed as a WildcardQuery and internally it's using this:
{Lucene.Net.Search.MultiTermQuery.AnonymousClassConstantScoreAutoRewrite}
Not sure why it's still using a Constant Score rewriter. Can someone explain?
I also believe I cannot use at search-time boosting as I don't need to boost certain terms, but certain documents (eg, most recent news appear first).
PS: This is not a duplicate of this question.
Nevermind. I was using a custom implementation of the QueryParser that had the method NewTermQuery overwritten.
Something like this:
protected override Query NewTermQuery(Term term)
{
var field = term.Field();
var text = term.Text() ?? "";
if (field == "contents" &&
text.Length >= 3 &&
text.IndexOfAny(new[] { '*', '?' }) < 0)
{
var wq = new WildcardQuery(new Term(field, text + "*"));
return wq;
}
return base.NewTermQuery(term);
}
And the WildcardQuery was not taking that configuration. All I had to do is call wq.SetMultiTermRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE); inside that if.

Linq OrderBy not sorting correctly 100% of the time

I'm using the Linq OrderBy() function to sort a generic list of Sitecore items by display name, then build a string of pipe-delimited guids, which is then inserted into a Sitecore field. The display name is a model number of a product, generally around 10 digits. At first it seemed like this worked 100% of the time, but the client found a problem with it...
This is one example that we have found so far. The code somehow thinks IC-30R-LH comes after IC-30RID-LH, but the opposite should be true.
I put this into an online alphabetizer like this one and it was able to get it right...
I did try adding StringComparer.InvariantCultureIgnoreCase as a second parameter to the OrderBy() but it did not help.
Here's the code... Let me know if you have any ideas. Note that I am not running this OrderBy() call inside of a loop, at any scope.
private string GetAlphabetizedGuidString(Item i, Field f)
{
List<Item> items = new List<Item>();
StringBuilder scGuidBuilder = new StringBuilder();
if (i != null && f != null)
{
foreach (ID guid in ((MultilistField)f).TargetIDs)
{
Item target = Sitecore.Data.Database.GetDatabase("master").Items.GetItem(guid);
if (target != null && !string.IsNullOrEmpty(target.DisplayName)) items.Add(target);
}
// Sort it by item name.
items = items.OrderBy(o => o.DisplayName, StringComparer.InvariantCultureIgnoreCase).ToList();
// Build a string of pipe-delimited guids.
foreach (Item item in items)
{
scGuidBuilder.Append(item.ID);
scGuidBuilder.Append("|");
}
// Return string which is a list of guids.
return scGuidBuilder.ToString().TrimEnd('|');
}
return string.Empty;
}
I was able to reproduce your problem with the following code:
var strings = new string[] { "IC-30RID-LH", "IC-30RID-RH", "IC-30R-LH", "IC-30R-RH"};
var sorted = strings.OrderBy(s => s);
I was also able to get the desired sort order by adding a comparer to the sort.
var sorted = strings.OrderBy(s => s, StringComparer.OrdinalIgnoreCase);
That forces a character-by-character (technically byte-by-byte) comparison of the two strings, which puts the '-' (45) before the 'I' (73).

Search a table with a lot of columns and then group the result with EntityFramework

I have an Asp.Net MVC 5 website and I'm using Entity Framework code first to access its database. I have a Restaurants table and I want to let users search these with a lot of parameters. Here's what I have so far:
public void FilterModel(ref IQueryable<Restaurant> model)
{
if (!string.IsNullOrWhiteSpace(RestaurantName))
{
model = model.Where(r => r.Name.ToUpper().Contains(RestaurantName));
}
if (Recommended)
{
model = model.Where(r => r.SearchSponsor);
}
//...
}
Basically I look for each property and add another Where to the chain if it's not empty.
After that, I want to group the result based on some criteria. I'm doing this right now:
private static IQueryable<Restaurant> GroupResults(IQueryable<Restaurant> model)
{
var groups = model.GroupBy(r => r.Active);
var list = new List<IGrouping<bool, Restaurant>>();
foreach (var group in groups)
{
list.Add(group);
}
if (list.Count < 1)
{
SortModel(ref model);
return model;
}
IQueryable<Restaurant> joined, actives, inactives;
if (list[0].FirstOrDefault().Active)
{
actives = list[0].AsQueryable();
inactives = list.Count == 2 ? list[1].AsQueryable() : null;
}
else
{
actives = list.Count == 2 ? list[1].AsQueryable() : null;
inactives = list[0].AsQueryable();
}
if (actives != null)
{
//....
}
if (inactives != null)
{
SortModel(ref inactives);
}
if (actives == null || inactives == null)
{
return actives ?? inactives;
}
joined = actives.Union(inactives).AsQueryable();
return joined;
}
This works but it's got a lot of complications which I rather not talk about for the sake of keeping this question small.
I was wondering if this is the right and efficient way to do it. It seems kind of "dirty"! Lots of ifs and Wheres. Stored procedures, inverted indices, etc. This is my first "big" project and I want to learn from your experience to do this the "right" way.
Looking at the GroupResults Method I get a little confused about what you are doing. It seems the intention is to receive an arbitrary list of restuarants and return an ordered list of restaurants ordered by Active and some other criteria.
If thats true you may just do something like this and your job's done:
model.OrderBy(x => x.Active).ThenBy(x => Name);
If SortModel is somehow more sophisticated you may either add a comparer to the statement or stick with your current solution but change it to this:
if (model == null || !model.Any())
{
return model;
}
var active = model.Where(x=>x.Active);
var inactives = model.Where(x=>!x.Active);
// if (inactives == null) //not needed as where always return at least an empty list. Mabye check for inactive.Any()
SortModel(ref inactives); //You may also remove the ref as it's an reference anyway
joined = actives.Union(inactives).AsQueryable();
return joined;
Regarding the way you are handling your searching, I think it is simple, easy to read and understand, and it works. New team members will be able to look at that code and know immediately what it is doing and how it works. I think that is a pretty good indication that your approach is sound.

Linq to entities query optimizing

I have 3 tables in my DB which I'm working with:
Theme [Theme_ID]
ThemeWorkplace [Theme_ID, Workplace_ID, ThemeWorkplace_ID]
UserTheme [User_ID, Theme_ID, UserTheme_ID, UserTheme_AccessType]
I need to change UserTheme_AccessType for all UserTheme.Theme_ID in current workplace with ThemeWorkplace.Workplace_ID = 2 and current user with User_ID = 1. If theme is no row in UserTheme for such user and such theme - I need to create it.
I wrote such a code, but it works too long time:
var themeList = (from t in m_Entities.Theme
where (from tw in m_Entities.ThemeWorkplace
where tw.Workplace.Workplace_ID == 2
select tw.Theme.Theme_ID).Contains(t.Theme_ID)
select t)
.ToList();
foreach (Theme theme in themeList)
{
var oldUserTheme = GetByUserTheme(user/*user is given*/, theme);
if (oldUserTheme == null)
{
/* create new User Theme with params, that I need*/
this.Add(newUserTheme, true);
}
else
{
/* here - changing found row */
oldUserTheme.UserTheme_AccessType = 2;
}
}
I understand that this code accesses the database too many times. I want to find a way to get rid of:
var oldUserTheme = GetByUserTheme(user/*user is given*/, theme);
In every foreach iteration. Could somebody please help me?
Adding code of GetByUserTheme():
private UserTheme GetByUserTheme(User user, Theme theme)
{
return m_Entities.UserTheme.FirstOrDefault(ut => ut.User.User_ID == user.User_ID && ut.Theme.Theme_ID == theme.Theme_ID);
}
First: All changes for entities that you have done in code will be pushed to database in one batch command when you call context.SaveChanges. So you will have one request to Database for select and one request for update.
But in your batch command will be many sql queries for cause EF generate sql for updates entities one by one (not all in one).
If you want update really many records in database, you should use sql script (call stored procedure or execute sqlquery) against using EntityFramework.
I don't know if I'm completely understanding your question and structure. But based on what I see, could this be a reasonable slution?
First you select the workplaces that have ID equal to 2. From that result you select the theme-ID's. All your userthemes that have a themeID that occurs in the former result will then be selected into 'userThemes'. From there, you iterate over the results and if the userID is empty you create a new UserTheme, otherwise you update it.
Quick note: the code below is no real working code. It's just code I wrote to exemplify my explanation, kind of pseudo-code if you will.. :)
var userThemes = entities.Userthemes
.Where(ut => entities.Workplaces
.Where(w => w.WorkPlaceID == 2)
.Select(s => s.ThemeID)
.Contains(ut.ThemeID));
foreach (UserTheme ut in userThemes)
{
if (ut.UserID.ToString() == "")
{
//Create
}
else
ut.UserThemeAccessType = 2;
}

Categories