I am writing a web app using angular for the front end, Breeze for the data management/middle layer, and ASP.net core and EFCore on the server side.
The problem I am encountering is that it seems that creating a query using the "IN" operator on an integer field, just doesn't seem to work.
I have simplified the code to just what is needed to reproduce the problem.
On the client side I have a breeze query that looks like this:
import { Injectable } from '@angular/core';
import { EntityManager, EntityQuery, FilterQueryOp, Predicate } from 'breeze-client';
import { Observable, of, from } from 'rxjs';
import { EntityManagerProvider } from './entity-manager-provider';
import { Verb } from './model/verb';
@Injectable({providedIn: 'root'})
export class VerbService {
manager: EntityManager
constructor(private entityManagerProvider: EntityManagerProvider) {
this.manager = entityManagerProvider.newManager();
}
getVerbs(): Observable<Verb[]> {
const query = new EntityQuery('Verbs')
.where("verbID", FilterQueryOp.In, [1, 2, 3]);
return from(
this.manager.executeQuery(query)
.then(data => { return <Verb[]>data.results }));
}
}
Which produces the following get request:
https://localhost:44356/api/breeze/Verbs?{"where":{"VerbID":{"in":[1,2,3]}}}
which as far as I can tell is correct.
The problem is that this causes the following error:
ArgumentException: Method 'Boolean Contains(System.String)'
declared on type 'System.Collections.Generic.List`1[System.String]'
cannot be called with instance of type 'System.Collections.Generic.List`1[System.Int32]'
Just to be clear nothing involved in this is a string or a List<string>
. VerbID
is an Int
and I'm passing in an array of Ints
.
If I change the client-side query to this:
const query = new EntityQuery('Verbs')
.where("verbID", FilterQueryOp.Equals, 1)
.orderBy("name");
which produces a request that looks like this:
https://localhost:44356/api/breeze/Verbs?{"where":{"VerbID":{"eq":1}}}
It works fine, so do all other types of queries I have tried that don't use the "in" operator.
For the sake of clarity, the requests are properly URL escaped I have just unescaped them to make them a bit easier to read.
I am just posting this to see if anyone can advise me if I am doing something wrong before I start debugging the Breeze source code to see if it is a bug.
Here is the server-side code for reference, which as you can see I am not really doing anything other than wrapping the breeze ASP.net Core/EF Core server-side stuff.
public class Verb
{
public int VerbID { get; set; }
public string Name { get; set; }
public string Translation { get; set; }
public string VerbTypeDescription { get; set; }
}
public class VerbsContext : DbContext
{
public VerbsContext(DbContextOptions<VerbsContext> options) : base(options)
{
}
public DbSet<Verb> Verbs { get; set; }
}
[Route("api/[controller]/[action]")]
[BreezeQueryFilter]
public class BreezeController : ControllerBase
{
private readonly VerbsPersistenceManager _persistenceManager;
public BreezeController(VerbsContext context)
{
_persistenceManager = new VerbsPersistenceManager(context);
}
public IQueryable<Verb> Verbs()
{
return _persistenceManager.Context.Verbs;
}
}
public class VerbsPersistenceManager : EFPersistenceManager<VerbsContext>
{
public VerbsPersistenceManager(VerbsContext context) : base(context) { }
}
I have done a bit of digging and it's definitely a bug, or more specifically the "IN" operator seems to be incomplete and at the moment is just a bit of a hack that only works on strings.
This is the offending line from the Breeze.Core source code:
else if (op == BinaryOperator.In)
{
// TODO: need to generalize this past just 'string'
var mi = TypeFns.GetMethodByExample((List<String> list) => List.Contains("abc"), expr1.Type);
return Expression.Call(expr2, mi, expr1);
}