I am querying lucene index via nhibernate.search using code below:
var fts = NHibernate.Search.Search.CreateFullTextSession(this._session);
var luceneQuery = "Search:name~0.7 AND Moderated:true NOT PlaceType:WrongType";
var places = fts.CreateFullTextQuery<Place>(luceneQuery)
.List<Place>();
The problem is that query returns all types of Places, including WrongType. When I try to run the same query against the same index in Luke everything is ok, Places of type WrongType are not returned.
Search field is concatenation of many fields in Place object. I am using Moderated and PlaceType fields to filter out some records, as I have discovered, that in this way original sorting order (by score) from Lucene query is preserved.
How can I exclude Places by PlaceType from results using NHibernate.Search?
Ok, so I have found solution.
I have indexed all fields using WhiteSpaceAnalyzer. It seems that NHibernate.Search is using StandardAnalyzer by default, regardless from the fact, that I have set global AnalyzerClass to WhiteSpaceAnalyzer. After parsing the query it looked like that:
"+Search:name~0.7 +Moderated:true -PlaceType:wrongtype"
which didn't work, because values in PlaceType field were not lowercased.
Changing the code in the question to something like that:
var fts = NHibernate.Search.Search.CreateFullTextSession(this._session);
var queryParser = new QueryParser("text", new WhitespaceAnalyzer());
var luceneQuery = "Search:name~0.7 AND Moderated:true NOT PlaceType:WrongType";
var query = queryParser.Parse(luceneQuery);
var places = fts.CreateFullTextQuery(query, typeof(Place))
.List<Place>();
solved the situation.
Related
I've tried to reduce this example to remove the OrganizationServiceProxy/XrmServiceContext, but now I believe the issue is originating there. I'm pulling data from a Dynamics 365 instance using the code generated by CrmSvcUtil.exe from Microsoft. Here is the relevant snippet:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
CrmServiceClient client = new CrmServiceClient(userName, CrmServiceClient.MakeSecureString(password), string.Empty, orgName, isOffice365: true);
using (OrganizationServiceProxy service = client.OrganizationServiceProxy)
{
XrmServiceContext crm = new XrmServiceContext(service);
var q = crm.CreateQuery<Account>();
List<Account> accounts = q.Where(x => x.AccountNumber != x.dynamics_integrationkey).ToList();
}
I get the following exception:
variable 'x' of type '{company-specific}.Account' referenced from scope '', but it is not defined
This is happening on the last line when the Linq is actually executed. It's only when it's created through the Microsoft.Xrm.Sdk.Client.OrganizationServiceContext that I get the error.
Things I've checked:
I get the error when comparing any two fields on the same object (not just Account), even when both are the same exact data type.
If I replace crm.CreateQuery... with a hand-populated List...AsQueryable() the Linq works fine
Most frustratingly, if I work through an intermediary list with List<Account> a = q.ToList(); to rasterize the result set first then this all works fine. It takes much longer to run because I've forfeited any lazy loading, but it works without error.
I've looked at other answers related to the referenced from scope '' but not defined Linq error, but they are all about malformed Linq. I know this Linq is well formed because it works fine if I change the underlying Queryable.
Clearly something is different between a List...ToQueryable() and the Microsoft.Xrm.Sdk.Linq.Query<> created by the XrmServiceContext (which inherits CreateQuery from Microsoft.Xrm.Sdk.Client.OrganizationServiceContext), but I don't know what it is.
The query you are trying to build (compare a field value with another field value on the same entity) is not possible in Dynamics.
The LINQ Provider still uses QueryExpression under the hood, meaning that your LINQ condition will be converted in a ConditionExpression, and that comparison is not possible.
Similar questions:
https://social.microsoft.com/Forums/en-US/d1026ed7-56fd-4f54-b382-bac2fc5e46a7/linq-and-queryexpression-compare-entity-fields-in-query?forum=crmdevelopment
Dynamics QueryExpression - Find entity records where fieldA equals fieldB
This morning I realized that my answer contained the flaw of comparing two fields against each other in a LINQ query. My bad. Thank you Guido for calling that out as impossible.
I generate my proxy classes with a 3rd party tool, so I'm not familiar with the XrmServiceContext class. But, I have confirmed that XrmServiceContext inherits from Microsoft.Xrm.Client.CrmOrganizationServiceContext, which inherits from Microsoft.Xrm.Sdk.Client.OrganizationServiceContext
I use OrganizationServiceContext for LINQ queries.
If you retrieve all the accounts first by calling ToList() on the query, you can then do the comparison of the two fields. If you have too many Accounts to load all at once, you can page through them and store the mismatched ones as you go.
And, I would probably retrieve a subset of the fields rather than the whole record (as shown).
var client = new CrmServiceClient(userName, CrmServiceClient.MakeSecureString(password), string.Empty, orgName, isOffice365: true);
using (var ctx = new OrganizationServiceContext(client))
{
var q = from a in ctx.CreateQuery<Account>()
where a.AccountNumber != null
&& a.dynamics_integrationkey != null
select new Account
{
Id = a.AccountId,
AccountId = a.AccountId,
Name = a.Name,
AccountNumber = a.AccountNumber,
dynamics_integrationkey = a.dynamics_integrationkey
};
var accounts = q.ToList();
var mismatched = accounts.Where(a => a.AccountNumber != a.dynamics_integrationkey).ToList()
}
I am using Seed() method of Configuration.cs class for filling data in database when using Update-Database command.
Among other things I am creating list of EventCategory objects like this:
private IList<EventCategory> CreateEventCategoriesTestDefinition()
{
eventCategories = new List<EventCategory>();
var eventCategoryRecruitment = new EventCategory("Recruitment");
eventCategories.Add(eventCategoryRecruitment);
var eventCategoryInternship = new EventCategory("Internship");
eventCategories.Add(eventCategoryInternship);
var eventCategoryTrainingPrograms = new EventCategory("Training Programs");
eventCategoryTrainingPrograms.Events
.Add(new Event("Managerial Training Program 2012-2014", eventCategoryTrainingPrograms));
eventCategories.Add(eventCategoryTrainingPrograms);
var eventCategoryEmployee = new EventCategory("Employee & Team Potential");
eventCategories.Add(eventCategoryEmployee);
return eventCategories;
}
Adding element by element. eventCategory is just a private property:
private IList<EventCategory> eventCategories;
From Seed() method I am calling CreateEventCategoriesTestDefinition()
Almost everything is good but when I go to database to check data I have noticed that data in EventCategory table doesn't have correct order:
As you can see it on a picture Internshipand Training Programs switched positions comparing to order of adding inCreateEventCategoriesTestDefinition() method.
Does anybody knows what is happening here? Why order of adding is not preserved? I know it should be perserved in List<>, but is not the same for IList<>?
Or this is maybe has something to do with EntityFramework?
If you are relying upon the database for your sorting order then either.
Turn auto-id incrementation off and specify your own ID
EventCategory(int id, string name)
If you have to use database identity then instead try using a sort order (int) column for your objects
EventCategory(string name, int sortOrder)
Either way, you cannot guarantee that you'll get a sorted List persisted to the database. I cant say for certain your use case here, but you shouldn't reply on SQL to sort your objects for you, but when querying the database use linq to order them before binding to a view.
e.g.
var mySortedCategories = dbContext.EventCategories.OrderBy(x => x.Name).ToList();
string rep = "Joe Shmoe"
ObjectSet<StoreData> storeData = edmContext.StoreData;
ObjectSet<CallData> callData = edmContext.CallData;
IEnumerable<string> repStoreData = storeData.Where(r => r.RepName == rep).Select(s => s.Location);
IEnumerable<CallData> repCallData = Here is where I want to filter down the callData collection down to just the records that have a location that is contained in the repStoreData collection
I've tried using some form of Join and Any but don't really understand the arguments those are asking for.
This was my best attempt and it is a no go.
... = callData.Join(d => d.LOCATION.Any(repStoreData));
Well you don't have to use a join. You could just use:
callData.Where(d => repStoreData.Contains(d.LOCATION))
That's assuming d.LOCATION is a single string.
However, you probably don't want to do that with your current declaration of repStoreData as IEnumerable<string> - LINQ won't be able to turn that into a query to be executed at the database.
If you're able to declare repStoreData as IQueryable<string>, however, that would be more likely to work well. I don't know whether that will work with ObjectSet<T>, but I'd hope so.
Hi i am trying to get to grips with Dapper.
My situation is i want to pull two values from a query into two separate strings. Im not sure if i am going about this in the correct way, but this is what i am doing:
string sql = #"Select type, name
FROM ZipData
WHERE Zip = #zip";
using (var multi = conn.QueryMultiple(sql, new { zip = zip }))
{
string result = multi.Read<string>().SingleOrDefault();
}
And i am getting Cannot access a disposed object. Object name: 'GridReader'. when trying to read the second string.The thing is it gets the first value correctly and has both the fields in in the reader i am trying to get. Im sure im misusing the api.
What am i doing wrong here? Ive googled but can find a specific example.
You are mis-using QueryMultiple. That is defined for compound SQL statements that return multiple result sets. Something like:
SELECT Foo FROM MyTable;
SELECT Bar FROM MyOtherTable;
On the other hand, you are trying to get two different columns from a single result set, so you should just use the normal Query method:
var result = conn.Query(sql, new { zip = zip }).Single();
var type = result.type;
var name = result.name;
Query returns an enumerable (because generally a query can return multiple rows). It appears that you only want one row, however, so we invoke .Single at the end to just get that row. From there, the return type is dynamic so you can simply refer to the properies implied by the columns in your SELECT statement: type and name.
In Luke, the following search expression returns 23 results:
docurl:www.siteurl.com docfile:Tomatoes*
If I pass this same expression into my C# Lucene.NET app with the following implementation:
IndexReader reader = IndexReader.Open(indexName);
Searcher searcher = new IndexSearcher(reader);
try
{
QueryParser parser = new QueryParser("docurl", new StandardAnalyzer());
BooleanQuery bquery = new BooleanQuery();
Query parsedQuery = parser.Parse(query);
bquery.Add(parsedQuery, Lucene.Net.Search.BooleanClause.Occur.MUST);
int _max = searcher.MaxDoc();
BooleanQuery.SetMaxClauseCount(Int32.MaxValue);
TopDocs hits = searcher.Search(parsedQuery, _max)
...
}
I get 0 results
Luke is using StandardAnalyzer and this is what the Explain Structure window looks like:
Must I manually create BooleanClause objects for each field I search on, specifying Should for each one then add them to the BooleanQuery object with .Add()? I thought the QueryParser would do this for me. What am I missing?
Edit:
Simplifying a tad, docfile:Tomatoes* returns 23 docs in Luke, yet 0 in my app. Per Gene's suggestion, I've changed from MUST to SHOULD:
QueryParser parser = new QueryParser("docurl", new StandardAnalyzer());
BooleanQuery bquery = new BooleanQuery();
Query parsedQuery = parser.Parse(query);
bquery.Add(parsedQuery, Lucene.Net.Search.BooleanClause.Occur.SHOULD);
int _max = searcher.MaxDoc();
BooleanQuery.SetMaxClauseCount(Int32.MaxValue);
TopDocs hits = searcher.Search(parsedQuery, _max);
parsedQuery is simply docfile:tomatoes*
Edit2:
I think I've finally gotten to the root problem:
QueryParser parser = new QueryParser("docurl", new StandardAnalyzer());
Query parsedQuery = parser.Parse(query);
In the second line, query is "docfile:Tomatoes*", but parsedQuery is {docfile:tomatoes*}. Notice the difference? Lower case 't' in the parsed query. I never noticed this before. If I change the value in the IDE to 'T', 23 results return.
I've verified that StandardAnalyzer is being used when indexing and reading the index. How do I force queryParser to keep the case of the value of query?
Edit3:
Wow, how frustrating. According to the documentation, I can accomplish this with:
parser.setLowercaseExpandedTerms(false);
Whether terms of wildcard, prefix,
fuzzy and range queries are to be
automatically lower-cased or not.
Default is true.
I won't argue whether that's a sensible default or not. I suppose SimpleAnalyzer should have been used to lowercase everything in and out of the index. The frustrating part is, at least with the version I'm using, Luke defaults the other way! At least I learned a bit more about Lucene.
Using Occur.MUST is equivalent to using the + operator with the standard query parser. Thus you code is evaluating +docurl:www.siteurl.com +docfile:Tomatoes* rather than the expression you typed into Luke. To get that behavior, try Occur.SHOULD when adding your clauses.
QueryParser will indeed take a query like "docurl:www.siteurl.com docfile:Tomatoes*" and build a proper query out of it (boolean query, range query, etc.) depending on the query given (see query syntax).
Your first step should be to attach a debugger and inspect the value and type of parsedQuery.