Dynamic "OR" for a full-text search

Hello!

Say I have 3 string fields and I want to search for the same string inside those fields.
I need a way to make a dynamic OR because the fields may be more than 3, or less and the OR search must work consistently, thus I can't do the Where(Linq.Contains('Prop1',Value) or... or...) but it needs to be dynamic, so the ORs have to be appended one after another.
Any ideas?

Thanks!

Maybe this topic can help you:

Oh, that might work. I will give it a try.
Does this work with normal expressions as well?

Thanks!

What do you mean by normal expressions? You can set any Linq expression to a variable of type TLinqExpression.

HA!

I can finally come back to this.
So, I followed what you said and now have a method which will gather up all the ORs and package them neatly in a TLinqExpression. So far, so good.
Now though I have to append or prepend this to my WHERE for the other criteria if I have any in a OR, because I may have a full text search but also filters, so this needs to be injected in the where that is built separately.
Any ideas of how to do this? There doesn't seem to be an underlying expression property that I can hook this up to.

Thanks!

Well, in the TCriteria object you have a property Criteria that lists all the expressions added to it:

    property Criteria: TList<TCustomCriterion> read FCriteria;

It would be up to you to parse, analyze and check which has OR statement and which one you want to add. It's very complex imho. You should refactor your code to tell your full search text logic to which expression to add more conditions.

What I am ideally looking to do is something like one of these:

  1. Criteria.Expression := Criteria.Expression or MyExpression
  2. Criteria._OR(MyExpression);

Because MyExpression already contains all the ORs that are required.

Cheers!

What if your criteria has multiple expressions? It's possible.

Oh it's not only possible, it's likely!
But to try to clarify what I am saying here:
the way I have done this means we have two "lumps": one lump is for filtering (Linq.GreaterOrEqual etc.) and another is the various Linq.Contains that constitute the full text search.
Thus, from a logical standpoint it's 2 expressions if you see what I mean.
Cheeers!

Here is a sample code about how to add an OR operation to an existing expression in the criteria. But how to find the criteria expression and where/what to do is up to your application logic.

procedure TForm3.Button1Click(Sender: TObject);
var
  Criteria: TCriteria;
  Expr: TCustomCriterion;
begin
  Criteria := Manager.Find<TCustomer>;
  Criteria.Add(Linq['Id'] = 2);
  Criteria.Add(Linq['Name'] = 'Foo');

  // Add or to the second expression
  Expr := Criteria.Criteria.ExtractAt(1);
  Criteria.Criteria.Add(TExpression.Or_(Expr, Linq['Name'] = 'other'));

Hi!

So, I have finally gotten around to doing this and am having an issue.
My code looks like this:

var
 // Other stuff here
  LocalCriteria: TCriteria; // Criteria to swap in if there is a quick search
  CriteriaIntf: IAureliusCriteria; // Interface allowing the swapping in
  Expr: TLinqExpression; // Expression to put in OR
  ExprIntf: IAureliusExpression; // Filter expression
  FilterVal: IFilterRawValue; // Raw value for the filter
begin
  // Other stuff here
  if Not QuickSearch.IsEmpty then
  begin
    LocalCriteria := ObjectManager.Find<TORM>;
    if Supports(ORM, IAureliusCriteria, CriteriaIntf) then
    begin
      if CriteriaIntf.HasCriteria then
        CriteriaIntf.SetCriteria(LocalCriteria);
      for var StrFilter in FStringFilters do
      begin
        Filter := StrFilter.Create;
        IAureliusCriteria( Filter ).SetCriteria(LocalCriteria); // Ensure the filter uses the right criteria.
        ExprIntf := Filter as IAureliusExpression; // Extract the Aurelius Expression interface from the filter
        Expr := Expr or ExprIntf.ExposeExpression; // OR the expression 
        FilterVal := Filter as IFilterRawValue; // Prepare to set the value
        FilterVal.RawValue := QuickSearch; // Set the value
      end;
      LocalCriteria.Where(Expr); // Add all to the where which is no (... OR... OR....etc.) 
    end;
  end;
  // More stuff here
end;

QuickSearch is a string parameter. When I execute the whole thing, I get an AV with the following call stack:


It appears that the visitor pattern is recurring onto itself because I have debugged it and that seems to be the only hint at play here.
Have you ever seen anything like this before?

Thanks!

Well, you have a lot of your own code there. You almost didn't show any pure Aurelius code, so I cannot tell what's going on. I'm afraid you will have to review your code or create a very minimum project reproducing the issue.

Note that the original code I sent you has an ExtractAt. You have to be careful about what you destroy, add and remove, because the manager and the criteria objects often destroy some objects automatically - manager destroys the criteria itself, and criteria destroys its expressions.

Hi!

Yes, there is a ton of custom code, I agree, but basically all of it is syntactic sugar to do things more easily and uniformly: for example, IAureliusExpression has a method called ExposedExpression which exposes the internal LINQ expression. Filtering, likewise, works with an internal TCriteria and a List and so on. There is very little in terms of code that does something "complex" and in most cases the classes simply implement field or table names to get the LINQ expressions right. Wherever there are multiple options (such as date ranges, for example) what I do is branch out for the correct LINQ expression instead of trying to be fancy and doing something too weird. The other thing is that I am not using ExtractAt because, as you say, doing that sort of thing is quite dangerous. What I am doing instead is pre-process the quick search so that the whole thing becomes one big bracketed expression to which I then append the various implicit "ANDs" by using the fluent interface.

So basically what I think I am doing is this:

  • Calculate the quick search expression as a set of ORs:: this is one expression that gets added to the where as first thing
  • Then I add the other filters as required, based on an auto-name that is determined by the filter class and use the raw value to set the appropriate filter value

Therefore at the end of the whole thing and before calling the list, I have this situation:
Criteria.Where( A and B) where A is an expression of all the ORs in the quick search and should therefore be seen by Aurelius as a single bracketed expression.
I have further debugged the thing and realised that

procedure TOrExpression.Accept(Visitor: TCriterionVisitor);
begin
  Visitor._Or(Self);
end;

Is the problem because Self in that point is nil, but this happens during the ORs and I cannot easily determine the actual flow. There are 7 string filters that are checked and when I go into the 7th Aurelius tries to do an OR and visit the internal tree but finds a NIL on the left side. Because of this, when it tries to visit it generates an AV.

Does this make sense?

Thanks!

Hard to tell. Looks like a complex code. I can't point what's going wrong, unfortunately. You wrote the code and you have access to all of it, and still you don't know what's going on. :slight_smile:

So please create a minimal project reproducing the issue that we can run at our side here and then I can check what's going on. My simple guess is that you are mixing up the handling of objects lifetime. Each expression holds and destroys its own sub expression, so possibly you are doing something wrong in that part. But again, I can only guess.