I am attempting to implement some customized searching in an application that allows a user to type in certain fields and terms and query for a result; So for instance, there is a text box and they can type... Name: Stacey and it runs some code.
That works fine, if they give it a valid name. But if they give it information it can't find, I get an exception and I can't figure out how to stop it. A try doesn't seem to work, either; The code isn't too complex, but it is a bit broken up.
Basically, this runs after receiving zero or two strings from a controller method. The first string is the field, and the second is the value to query for. If I give it valid input, it works fine; But anything else causes the program to crash. Is there anything I can do about this?
The long-winded callback process is because each controller method needs to react differently to the results, but that's not related to this problem.
private JsonResult JsonDataFromDataSourceRequest<T, TIndexCreator>
([DataSourceRequest] DataSourceRequest request,
Func<IDocumentQuery<T>, string, string, IDocumentQuery<T>> search,
Func<IDocumentQuery<T>, IDocumentQuery<T>> sort)
where T : IHasName
where TIndexCreator : Raven.Client.Indexes.AbstractIndexCreationTask, new() {
RavenQueryStatistics statistics;
var query = RavenSession
.Advanced.DocumentQuery<T, TIndexCreator>()
.WhereEquals("collection", typeof(T).RavenCollection())
.WaitForNonStaleResultsAsOfNow()
.Statistics(out statistics); // output our query statistics
// we can accept a sorting system, or additional query options here
if (sort != null)
query = sort(query);
if (request.Filters != null) {
if (request.Filters.Any()) {
var filters = request.Filters.ToFilters();
// determine the field/term to search by
var name = filters.Count > 0 ? filters[0].Value.ToString() : null;
var field = filters.Count > 0 ? filters[0].Member.ToString() : "Name";
if (name != null && !String.IsNullOrEmpty(name)) {
query = query.AndAlso().Search(field, name);
}
}
}
var results = query
.OrderBy(n => n.Name)
.Skip((request.Page - 1) * request.PageSize)
.Take(request.PageSize)
.ToList();
var totalResults = statistics.TotalResults;
return Json(new { data = results, total = totalResults });
}
Update
System.InvalidOperationException
The exception thrown is System.InvalidOperationException. When I drill down on the debugger, the actual query sent to Raven is in the form of;
{collection:users AND DisplayName: ( Stacey)}
It's kind of a mixed bag; Let me explain a few things.
collection exists because this index is used for a lot of different objects, but many, if not all of them, share several similar fields. It seemed simpler to just make the collection a part of the query than try to find a way to use the Type in the Query<T> call.
Is it possible to keep the flexibility and give the query a specific TModel to query against? I wanted to avoid making dozens of repetitive indexes.
This does appear to happen when the user tries to lookup a field that is not on the type of object being examined.
In other words, even though Owner is indexed, if the user types Owner: Ciel against a page that looks up Users, which do not have the Owner field (but many, many other classes do), it throws this error. It does this if they try to type in a field that is not indexed as well.
Is it possible to return nothing if the query fails?
Here is a look at the index I am using. Perhaps there is a simpler way to do this without the collection field, but I'm not aware of it off the top of my head (but I'm still inexperienced). There are almost 28 different pages, each with search functionality. There are so many similar fields between some of the entities that it seemed pointless to make 28 different indexes.
public class EntityByName : AbstractIndexCreationTask {
public override IndexDefinition CreateIndexDefinition() {
return new IndexDefinition {
Map = @"
from doc in docs
let collection = doc[""@metadata""][""Raven-Entity-Name""]
select new {
doc.Id,
doc.Name,
doc.Owner,
doc.Group,
doc.Email,
Tags = doc.Tags.Select( r => r.Name ),
collection
};",
Indexes ={
{"Id", FieldIndexing.Analyzed},
{"Name", FieldIndexing.Analyzed},
{"Owner", FieldIndexing.Analyzed},
{"Tags", FieldIndexing.Analyzed},
{"Group", FieldIndexing.Analyzed},
{"Email", FieldIndexing.Analyzed}
}
};
}
public override string IndexName {
get { return "Raven/DocumentsByEntity"; }
}
}
Exception Message
Here is a full exception message.
Url: "/databases/dev-isolated/indexes/Raven/DocumentsByEntity?&query=collection%3Ausers%20AND%20Number%3A%28%203%29&pageSize=5&sort=Name&SortHint-collection=String&SortHint-Name=String&cutOff=2015-10-02T15%3A12%3A31.5784892Z&waitForNonStaleResultsAsOfNow=true"
System.ArgumentException: The field 'DisplayName' is not indexed, cannot query on fields that are not indexed
at Raven.Database.Indexing.Index.AssertQueryDoesNotContainFieldsThatAreNotIndexed(IndexQuery indexQuery, AbstractViewGenerator viewGenerator) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Indexing\Index.cs:line 1130
at Raven.Database.Indexing.Index.IndexQueryOperation.<Query>d__5a.MoveNext() in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Indexing\Index.cs:line 1252
at Raven.Database.Util.ActiveEnumerable`1..ctor(IEnumerable`1 enumerable) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Util\ActiveEnumerable.cs:line 16
at Raven.Database.Actions.QueryActions.DatabaseQueryOperation.Init() in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Actions\QueryActions.cs:line 245
at Raven.Database.Actions.QueryActions.<>c__DisplayClasse.<Query>b__a(IStorageActionsAccessor accessor) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Actions\QueryActions.cs:line 118
at Raven.Storage.Esent.TransactionalStorage.ExecuteBatch(Action`1 action, EsentTransactionContext transactionContext) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Storage\Esent\TransactionalStorage.cs:line 843
at Raven.Storage.Esent.TransactionalStorage.Batch(Action`1 action) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Storage\Esent\TransactionalStorage.cs:line 807
at Raven.Database.Actions.QueryActions.Query(String index, IndexQuery query, CancellationToken externalCancellationToken) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Actions\QueryActions.cs:line 108
at Raven.Database.Server.Controllers.IndexController.PerformQueryAgainstExistingIndex(String index, IndexQuery indexQuery, Etag& indexEtag, HttpResponseMessage msg, CancellationToken token) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Server\Controllers\IndexController.cs:line 625
at Raven.Database.Server.Controllers.IndexController.ExecuteQuery(String index, Etag& indexEtag, HttpResponseMessage msg, CancellationToken token) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Server\Controllers\IndexController.cs:line 570
at Raven.Database.Server.Controllers.IndexController.GetIndexQueryResult(String index, CancellationToken token) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Server\Controllers\IndexController.cs:line 541
at Raven.Database.Server.Controllers.IndexController.IndexGet(String id) in c:\Builds\RavenDB-Stable-3.0\Raven.Database\Server\Controllers\IndexController.cs:line 182
at lambda_method(Closure , Object , Object[] )
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()
Fix
I have made the appropriate changes, this is the final result of the method;
private JsonResult JsonDataFromDataSourceRequest<T, TIndexCreator>
([DataSourceRequest] DataSourceRequest request,
Func<IDocumentQuery<T>, string, string, IDocumentQuery<T>> search,
Func<IDocumentQuery<T>, IDocumentQuery<T>> sort)
where T : IHasName
where TIndexCreator : Raven.Client.Indexes.AbstractIndexCreationTask, new() {
try {
// we need to be able to catch some query statistics to make sure that the
// grid view is complete and accurate, with paging
RavenQueryStatistics statistics;
// try to query the items listing as quickly as we can, getting only the
// page we want out of it
var query = RavenSession
.Advanced.DocumentQuery<T, TIndexCreator>()
.WhereEquals("collection", typeof(T).RavenCollection())
.WaitForNonStaleResultsAsOfNow()
.Statistics(out statistics); // output our query statistics
// we can accept a sorting system, or additional query options here
if (sort != null)
query = sort(query);
// deserialize the contents of the kendo grid filter
if (request.Filters != null) {
if (request.Filters.Any()) {
var filters = request.Filters.ToFilters();
var name = filters.Count > 0 ? filters[0].Value.ToString() : null;
var field = filters.Count > 0 ? filters[0].Member.ToString() : "Name";
if (name != null && !String.IsNullOrEmpty(name)) {
query = search(query, field, name);
}
}
}
// finish constructing the items query
var results = query
.OrderBy(n => n.Name)
.Skip((request.Page - 1) * request.PageSize)
.Take(request.PageSize)
.ToList();
var totalResults = statistics.TotalResults;
return Json(new { data = results, total = totalResults, errors = "" });
}
catch {
// the user tried to ask for something
// that doesn't exist, or a field that
// cannot be queried
var results = new List<T>();
// total results is always 0 on an error
var totalResults = 0;
var errors = new List<string> {
"You have attempted to query an invalid field, or given an inappropriate value."
};
return Json(new { data = results, total = totalResults, errors = errors });
}
}
System.InvalidOperationException- Cieltry/catchblock does seem to stop the problem but... something tells me that's not a good solution. - Ciel