Perform wildcard search of all (displayed) model fields in MVC? - asp.net-mvc

I have an MVC5 View where I am using the Grid.MVC component (https://gridmvc.codeplex.com/). This allows me to easily display data from my Oracle DB and has out of the box functionality for Sorting/Filtering each Data Column. What I am trying to implement now is a Wildcard Search across all fields in my grid. For example, if I search the number "2" I'd like to return all records that contain a "2" be they string, decimal, or DateTime.
The filter capability on this grid performs filtering (for individual columns) partially by modifying the URL (http://homeURL/?grid-filter=Location.DEPT__1__accounting) such as 1 being Equals, 2 being Contains, 3 being StartsWith, and 4 being EndsWith and then after the next 2 underscores being the search criteria.
I first thought I was going down the right path by using JavaScript to modify to the desired URL via daisy-chaining all fields with the search criteria using a CONTAINS. I then noticed that decimal fields like [Cost] and DateTime (Oracle DB) fields like [Acquired_Date] have criteria settings of Equals, Greater Than, and Less Than, so I tried:
$('#SearchIcon').on("click", function (e) {
window.location = window.location.href.substr(0, window.location.href.indexOf('?'));
window.location = "?grid-filter=FIELD1__2__" + document.getElementById("Search").value +
"&grid-filter=FIELD2__2__" + document.getElementById("Search").value +
"&grid-filter=COST__1__" + document.getElementById("Search").value +
// etc. -- ALL FIELDS
"&grid-filter=NOTE__2__" + document.getElementById("Search").value;
});
This technically functions, but with the [&] is searching for a record(s) that have the corresponding search criteria in EVERY field. What I need is something similar, but with an OR [||] conditioning ---- unfortunately the grid component does not contain this form of functionality.
I then thought to pass the search criteria to a controller action and use it via a multi-WHERE clause and return only the records fitting the filter to my View:
public ActionResult SearchAssets(string searchCriteria)
{
fillPagingIntervalList();
var assetSearchResults = db.ENTITY_COLLECTION.Where(m => m.ID.ToString() == searchCriteria ||
m.Model.ToString() == searchCriteria ||
m.COST.ToString() == searchCriteria ||
// etc. -- ALL FIELDS
).FirstOrDefault();
var assetCount = db.ENTITY_COLLECTION.ToList().Count();
return View(assetSearchResults);
}
This resulted in an error with the WHERE cluase, stating to view the Inner Exception for details -- ORA-12704: character set mismatch MVC. I then reduced my multiple conditions down to just 2 fields to be searched for debugging:
var assetSearchResults = db.ENTITY_COLLECTION.Where(m => m.ID.ToString() == searchCriteria ||
m.Model.ToString() == searchCriteria).FirstOrDefault();
Resulting in:
EntityCommandExecutionException was unhandled by user code.
An exception of type 'System.Data.Entity.Core.EntityCommandExecutionException' occurred in EntityFramework.dll but was not handled in user code
Additional information: An error occurred while executing the command definition. See the inner exception for details.
Inner Exception: ORA-00932: inconsistent datatypes: expected - got NCLOB
Anyone have an idea on how to get what I want working? I also tried .Where(...con1...).Where(...con2...).Where(...etc...) with the same error resulting. I figured a wildcard search across all fields would be difficult to implement, but this is proving to be a whole bigger animal than I anticipated.

This will be very slow, but try this, which will load the entire collection into objects and let LINQ do the filtering on the client side:
public ActionResult SearchAssets(string searchCriteria)
{
fillPagingIntervalList();
var assetSearchResults = db.ENTITY_COLLECTION.ToList().Where(m => m.ID.ToString() == searchCriteria ||
m.Model.ToString() == searchCriteria ||
m.COST.ToString() == searchCriteria ||
// etc. -- ALL FIELDS
).FirstOrDefault();
var assetCount = db.ENTITY_COLLECTION.ToList().Count();
return View(assetSearchResults);
}
You could try something like this:
public ActionResult SearchAssets(string searchCriteria)
{
fillPagingIntervalList();
var assetSearchResults = db.ENTITY_COLLECTION.Where(m => m.ID.ToString() == searchCriteria)
.Union(db.ENTITY_COLLECTION.Where(m =>m.Model.ToString()==searchCriteria))
.Union(db.ENTITY_COLLECTION.Where(m =>m.COST.ToString() == searchCriteria))
// etc. -- ALL FIELDS
var assetCount = db.ENTITY_COLLECTION.ToList().Count();
return View(assetSearchResults);
}
Although, ultimately I would suggest looking into something like a predicate builder. Seems to be what you are doing anyhow.

Related

BeanItemContainer unique property values

I am using BeanItemContainer for my Grid. I want to get a unique list of one of the properties. For instance, let's say my beans are as follows:
class Fun {
String game;
String rules;
String winner;
}
This would display as 3 columns in my Grid. I want to get a list of all the unique values for the game property. How would I do this? I have the same property id in multiple different bean classes, so it would be nice to get the values directly from the BeanItemContainer. I am trying to avoid building this unique list before loading the data into the Grid, since doing it that way would require me to handle it on a case by case basis.
My ultimate goal is to create a dropdown in a filter based on those unique values.
There isn't any helper for directly doing what you ask for. Instead, you'd have to do it "manually" by iterating through all items and collecting the property values to a Set which would then at the end contain all unique values.
Alternatively, if the data originates from a database, then you could maybe retrieve the unique values from there by using e.g. the DISTINCT keyword in SQL.
In case anyone is curious, this is how I applied Leif's suggestion. When they enter the dropdown, I cycle through all the item ids for the property id of the column I care about, and then fill values based on that property id. Since the same Grid can be loaded with new data, I also have to "clear" this list of item ids.
filterField.addFocusListener(focus->{
if(!(filterField.getItemIds() instanceof Collection) ||
filterField.getItemIds().isEmpty())
{
BeanItemContainer<T> container = getGridContainer();
if( container instanceof BeanItemContainer && getFilterPropertyId() instanceof Object )
{
List<T> itemIds = container.getItemIds();
Set<String> distinctValues = new HashSet<String>();
for(T itemId : itemIds)
{
Property<?> prop = container.getContainerProperty(itemId, getFilterPropertyId());
String value = null;
if( prop.getValue() instanceof String )
{
value = (String) prop.getValue();
}
if(value instanceof String && !value.trim().isEmpty())
distinctValues.add(value);
}
filterField.addItems(distinctValues);
}
}
});
Minor point: the filterField variable is using the ComboBoxMultiselect add-on for Vaadin 7. Hopefully, when I finally have time to convert to Vaadin 14+, I can do something similar there.

Filtering list using linq and mvc

Below is the code in question. I receive Object reference not set to an instance of an object. on the where clause inside the Linq query. However, this only happens after it goes through and builds my viewpage.
Meaning: If I step through using debugger, I can watch it pull the correct order I am filtering for, go to the correct ViewPage, fill in the model/table with the correct filtered item, and THEN it comes back to my Controller and shows me the error.
public ActionResult OrderIndex(string searchBy, string search)
{
var orders = repositoryOrder.GetOpenOrderList();
if (Request.QueryString["FilterOrderNumber"] != null)
{
var ordersFiltered = from n in orders
where n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n;
return View(ordersFiltered);
}
return View(orders);
}
its always better to manipulate your strings and other things outside the linq query ,
please refer : http://msdn.microsoft.com/en-us/library/bb738550.aspx
from the readability point of view also its not good ,
public ActionResult OrderIndex(string searchBy, string search)
{
var orders = repositoryOrder.GetOpenOrderList();
var orderNumber = Request.QueryString["FilterOrderNumber"];
if (!string.IsNullOrEmpty(orderNumber))
{
orderNumber = orderNumber.ToUpper();
var ordersFiltered = from n in orders
where n.OrderNumber.ToUpper().Contains(orderNumber)
select n;
return View(ordersFiltered);
}
return View(orders);
}
Your query is not being executed in your Action method because you don't have a ToList (or equivalent) added to your query. When your code returns, your query will be enumerated somewhere in your view and that's the point where the error occurs.
Try adding ToList to your query like this to force query execution in your action method:
var ordersFiltered = (from n in orders
where n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n).ToList();
What's going wrong is that a part of your where clause is null. This could be your query string parameter. Try moving the Request.QueryString part out of your query and into a temporary variable. If that's not the case make sure that your orders have an OrderNumber.
You both were right. Just separately.
This fixed my problem
var ordersFiltered = (from n in orders
where !string.IsNullOrEmpty(n.OrderNumber) && n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n);

LINQ query with omitted user input

so I have a form with several fields which are criteria for searching in a database.
I want to formulate a query using LINQ like so:
var Coll = (from obj in table where value1 = criteria1 && value2 = criteria2...)
and so on.
My problem is, I don't want to write it using If statements to check if every field has been filled in, nor do I want to make separate methods for the various search cases (criteria 1 and criteria 5 input; criteria 2 and criteria 3 input ... etc.)
So my question is: How can I achieve this without writing an excessive amount of code? If I just write in the query with comparison, will it screw up the return values if the user inputs only SOME values?
Thanks for your help.
Yes, it will screw up.
I would go with the ifs, I don't see what's wrong with them:
var query = table;
if(criteria1 != null)
query = query.Where(x => x.Value1 == criteria1);
if(criteria2 != null)
query = query.Where(x => x.Value2 == criteria2);
If you have a lot of criteria you could use expressions, a dictionary and a loop to cut down on the repetitive code.
In an ASP.NET MVC app, chances are your user input is coming from a form which is being POSTed to your server. In that case, you can make use of strongly-typed views, using a viewmodel with [Required] on the criteria that MUST be provided. Then you wrap your method in if (ModelState.IsValid) { ... } and you've excluded all the cases where the user hasn't given you something they need.
Beyond that, if you can collect your criteria into a list, you can filter it. So, you could do something like this:
filterBy = userValues.Where(v => v != null);
var Coll = (from obj in table where filterBy.Contains(value1) select obj);
You can make this more complex by having a Dictionary (or Lookup for non-unique keys) that contains a user-entered value along with some label (an enum, perhaps) that tells you which field they're filtering by, and then you can group them by that label to separate out the filters for each field, and then filter as above. You could even have a custom SearchFilter object that contains other info, so you can have filters with AND, NOT and OR conditions...
Failing that, you can remember that until you trigger evaluation of an IQueryable, it doesn't hit the database, so you can just do this:
var Coll = (from obj in table where value1 == requiredCriteria select obj);
if(criteria1 != null)
{
query = query.Where(x => x.Value1 == criteria1);
}
//etc...
if(criteria5 != null)
{
query = query.Where(x => x.Value5 == criteria5);
}
return query.ToList();
That first line applies any criteria that MUST be there; if there aren't any mandatory ones then it could just be var Coll = table;.
That will add any criteria that are provided will be applied, any that aren't will be ignored, you catch all the possible combinations, and only one query is made at the end when you .ToList() it.
As I understand of your question you want to centralize multiple if for the sake of readability; if I were right the following would be one of some possible solutions
Func<object, object, bool> CheckValueWithAnd = (x, y) => x == null ? true : x==y;
var query = from obj in table
where CheckValue(obj.value1, criteria1) &&
CheckValue(obj.value2, criteria2) &&
...
select obj;
It ls flexible because in different situations or scenarios you can change the function in the way that fulfill your expectation and you do not need to have multiple if.
If you want to use OR operand in your expression you need to have second function
Func<object, object, bool> CheckValueWithOr = (x, y) => x == null ? false : x==y;

Using a query in an Adapter for listview

I have a listview that I fill from an Adapter. My original code the data was being returned from a table, but now I need to get the code from a query with a join so the examples I used will no longer work and I haven't been able to find out how to use a query for this. I'm using an ORMrepository.
In my ORMrepository I have this function
public IList<Coe> GetmyCoe()
{
using (var database = new SQLiteConnection(_helper.WritableDatabase.Path))
{
string query = "SELECT Coe.Id, Adult.LName + ', ' + Adult.MName AS Name, Coe.Createdt FROM Adult INNER JOIN Coe ON Adult.CoeMID = Coe.Id";
return database.Query<Coe>(query);
}
}
which actually returns the data I want.
then in my Activity page I have this.
_list = FindViewById<ListView>(Resource.Id.List);
FindViewById<ListView>(Resource.Id.List).ItemClick += new System.EventHandler<ItemEventArgs>(CoeList_ItemClick);
var Coe = ((OmsisMobileApplication)Application).OmsisRepository.GetmyCoe();
_list.Adapter = new CoeListAdapter(this, Coe);
My Adapter page is where I have the problem, I know it is set up to to looking at a table which I'm not doing anymore. But I don't know how to change it for what I'm passing into it now. Current CoeListAdapter is:
public class CoeListAdapter : BaseAdapter
{
private IEnumerable<Coe> _Coe;
private Activity _context;
public CoeListAdapter(Activity context, IEnumerable<Coe> Coe)
{
_context = context;
_Coe = Coe;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var view = (convertView
?? _context.LayoutInflater.Inflate(
Resource.Layout.CoeListItem, parent, false)
) as LinearLayout;
var Coe = _Coe.ElementAt(position);
view.FindViewById<TextView>(Resource.Id.CoeMID).Text = Coe.Id.ToString();
//view.FindViewById<TextView>(Resource.Id.GrdnMaleName).Text = Coe.Name;
view.FindViewById<TextView>(Resource.Id.CreateDt).Text = Coe.CreateDt;
return view;
}
public override int Count
{
get { return _Coe.Count(); }
}
public Coe GetCoe(int position)
{
return _Coe.ElementAt(position);
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return position;
}
}
How do I set up the CoeListAdapter.cs page so that it can use the passed in data. As you can see I have a commented out lines where I fill a TextView which error because Coe.Name is not in the table model for Coe. but it is returned in the query. I believe my problem is IEnumerable but what do I change it to. I'm new to Mobile developement and suing VS2010 for Mono
The problem probably lies with the binding/mapping of the object not the creation of the view.
Or probably more specifically, the query.
Adult.LName + ', ' + Adult.MName AS Name
this should be:
Adult.LName || ', ' || Adult.MName AS Name
See also: String concatenation does not work in SQLite
From the sqlite docs: http://www.sqlite.org/lang_expr.html under the Operators heading:
The unary operator + is a no-op. It can be applied to strings,
numbers, blobs or NULL and it always returns a result with the same
value as the operand.
Note that there are two variations of the equals and not equals
operators. Equals can be either = or ==. The non-equals operator can
be either != or <>. The || operator is "concatenate" - it joins
together the two strings of its operands. The operator % outputs the
value of its left operand modulo its right operand.
The result of any binary operator is either a numeric value or NULL,
except for the || concatenation operator which always evaluates to
either NULL or a text value.
This shows that the + will evaluate to zero. If you use ||, the value will either be the correct value or NULL (if either of Adult.LName or Adult.MName is NULL).
This can be fixed by:
coalesce(first, '') || ', ' || coalesce(second, '')
but this may result in , LastName or FirstName,.
Another way would be to create another two properties in Coe called LName and MName.
Then bind the values to those properties and use the Name property like this:
public string Name
{
get { return string.Join(", ", LName, MName); }
}
This will probably be better as you can change how the Name appears especially if there are different combinations of First, Middle and Last names in different places.
And off topic:
I believe my problem is IEnumerable...
This is probably not too true as it returns the correct values. A better way would be to use IList as IEnumerable will iterate through the list each time to get the item as it does not know that the collection is actually a list. (I think)
thanks for the help on the concantination, I did find that was wrong, I did fix my problem, I was using an example by Greg Shackles on how to set up using a data base. what I had to do was create a new model with the elements I was wanting. So I created a new Model and called it CoeList, then everywhere I had List or IEnumerable I changed it to List or IEnumerable and it worked.

MVC Page to Accept Different Query Arguments

I have a results view that displays a list of items.
In some cases, I want to display items where one field matches the query argument. In other cases, I want to display items where another field matches the query argument. And so on.
How can I provide arguments to my controller/view so that it knows what kind of results to get? I could write multiple controllers, but then I get the error that "Type 'MyType' already defines a member called 'MyMethod' with the same parameter type."
Ideally, I could find a way to do this without creating additional routes. Is this possible?
From what I understand from your question you want to do the following:
Reutilize a view, so that you don't have to make one for each query.
Use the same method name on a controller so that you don't have a different url for each query.
You could add an extra parameter to your method so that you can perform a different query depending on the value of the extra parameter:
public class QueryController
{
private enum QueryType
{
TypeA = 0,
TypeB = 1,
TypeC = 2
}
[HttpGet]
public void ShowResults(QueryType type, string criteria)
{
/*
* Code here to make the query using the field you want,
* depending on the "type" parameter e.g.
*
* switch (type)
* {
* case TypeA:
* model = db.Items.Where(x => x.FieldA == criteria);
* break;
* case TypeB:
* model = db.Items.Where(x => x.FieldB == criteria);
* break;
* }
*/
return View(model);
}
}
Another thing, don't worry about the routes, the default route {controller}/{method}/{id} doesn't demand that you have to specify the URLs in that strict way. This is a valid URL for the previous example:
http://www.example.com/App/Query/ShowResults?type=1&criteria=foo
The default MVC binder will use the parameter names in the URL to bind their values to the parameters of your method.
I hope I didn't misunderstood your question.
Good luck!
With MVC, query string values map to parameter values (and Routes are used to create custom mappings).
There are special exceptions though. If a parameter is a FormCollection (which is basically just a Dictionary<string>), then all un-mapped query string values will be passed in.
So, you can create a single Action that takes in all parameters:
// GET: MyController/ViewList?name=aaa&field1=foo&field2=bar
public ActionResult ViewList(string name, FormCollection otherFields) {
foreach (var field in otherFields) {
switch (field.Key.ToLower()) {
case "field1":
// Filter field1 based on field.Value
case "field2":
// Filter field2 based on field.Value
...
}
}
...
}
Similarly, you could also have no parameters, and just use this.Request.QueryString to inspect the query string values. However, I prefer the FormCollection method because it makes it clear that I expect other parameters.

Resources