1
votes

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 });
        }
    }
1
what is the actual exception trown? - Ayende Rahien
Okay, I have updated the question with much more information, including more detailed explanations, the index being used, and the exception - Ciel
Sorry, to also answer you directly, the exception is System.InvalidOperationException - Ciel
after trying a few more things, wrapping the WHOLE thing in a big ugly try/catch block does seem to stop the problem but... something tells me that's not a good solution. - Ciel

1 Answers

2
votes

The error is pretty clear:

The field 'DisplayName' is not indexed, cannot query on fields that are not indexed