I'm struggling with expression trees and Entity Framework Core.
I have a method that returns an expression tree that I will use for filtering, something like:
public Expression<Func<E, bool>> GetWherePredicate<E>(Func<E, NpgsqlTsVector> selector, string queryText)
{
return entity => selector(entity).Matches(queryText);
}
And then I'd like to invoke this method with something like:
query = query.Where(GetWherePredicate<MyEntity>(i => i.MySearchField, "the_query"));
This produces an error, something like:
System.InvalidOperationException: The LINQ expression 'DbSet()
.Where(i => Invoke(__selector_0, i)
.Matches(__queryText_1))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
While I understand why this doesn't work, I am not sure how to solve this, but suspect it has to do with using expression trees. I thought of creating a new function that has the following signature, something like:
Expression<Func<E, bool>> GetWherePredicate<E>(MemberExpression selectorForSearchField, string queryText);
But I am not able to figure out how to take that expression and apply the Matches function.
Any help is appreciated.
Thanks,
Eric
The following is a way to answer the question, but it is not as simple as I would like. Or more correctly, my intuition tells me there is a cleaner and simpler approach.
My (not so simple) approach is the following:
Create a function that takes a MemberExpression (not a function which selects the property) that looks something like the following:
public Expression<Func<E, bool>> GetWherePredicate<E>(
MemberExpression member,
string queryText)
{
// Get the parameter from the member
var parameter = GetParameterExpression(member);
// Convert the query text into a constant expression
var queryTextExpression = Expression.Constant(queryText, typeof(string));
// Create an expression for the matches function
ftsMatchesFunction = typeof(NpgsqlFullTextSearchLinqExtensions).GetMethod("Matches",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
null,
new[] { typeof(NpgsqlTsVector), typeof(NpgsqlTsQuery) },
null);
var matchesExpression = Expression.Call(ftsMatchesFunction, member, partialSearchExpression);
return Expression.Lambda<Func<E, bool>>(matchesExpression, parameter);
}
And then to add the predicate, I have something like:
var predicate = PredicateBuilder.New<MyEntity>(false);
var myEntity= Expression.Parameter(typeof(MyEntity), "e");
var childEntity= Expression.PropertyOrField(myEntity, nameof(Invoice.Child));
var searchProperty = Expression.PropertyOrField(childEntity, nameof(Child.FtsSearchVector));
predicate = predicate.Or(_context.GetWherePredicate<MyEntity>(
searchProperty,
"the_query_text"));
And finally to add the filter to the query:
query = query.Where(predicate);
The cleaner or simpler solution is one in which does not need Expression Trees, as the only reason expression trees are needed is because I am not able to figure out how to select the search property in a way that ef can understand.
how to use the predicate to create an expression for retrieving objects from a list.
Method 1
Use the Predicate<generic> type and IEnumerable
[Fact]
public async Task TestPredicate()
{
List<Product> products = new List<Product>
{
new Product {ProductID=1,Name="Kayak",Category="Watersports",Price=275m},
new Product {ProductID=2,Name="Lifejacket", Category="Watersports",Price=48.95m},
new Product {ProductID=3,Name="Soccer Ball", Category="Soccer",Price=19.50m},
new Product {ProductID=4,Name="Corner Flag", Category="Soccer",Price=34.95m}
};
Predicate<Product> predicate = x => x.ProductID==4;
var query=Enumerable.FirstOrDefault(products,x=>predicate(x));
if (query != null) {
output.WriteLine(query.Name);
Assert.True(true);
}
}
Method 2
use Expression<Func pattern for your predicate:
Expression<Func<TimeAndAttendancePunchIn, bool>> predicate = e => e.EmployeeId == employeeId;
PageListViewContainer<TimeAndAttendancePunchInView> container = await taMod.TimeAndAttendance.Query().GetViewsByPage(predicate, order, pageSize, pageNumber);
public async Task<PageListViewContainer<TimeAndAttendancePunchInView>> GetViewsByPage(Expression<Func<TimeAndAttendancePunchIn, bool>> predicate, Expression<Func<TimeAndAttendancePunchIn, object>> order, int pageSize, int pageNumber)
{
var query = _unitOfWork.timeAndAttendanceRepository.GetEntitiesByExpression(predicate);
query = query.OrderByDescending(order).Select(e => e);
IPagedList<TimeAndAttendancePunchIn> list = await query.ToPagedListAsync(pageNumber, pageSize);
repository
public IQueryable<TimeAndAttendancePunchIn> GetEntitiesByExpression(Expression<Func<TimeAndAttendancePunchIn, bool>> predicate)
{
var result = _dbContext.Set<TimeAndAttendancePunchIn>().Where(predicate);
return result;
}
I'm trying to assign a non-generic map to a generic map, but flow complains that the value is incompatible. Is there anyway around this. Look at m4 and m5 in the example below.
interface Person {
name: string;
}
type Doctor = {
name: string,
license: string,
}
var d:Doctor = {
name: 'Sam',
license: 'PHD'
};
var p: Person = d;
// It is possible to create a generic array where each element
// implements the interface Person
const a: Array<Person> = [d];
// As a Map, it appears you cannot the value cannot be generic array
let m2: Map<string, Array<Doctor>> = new Map<string, Array<Doctor>> ();
let m3: Map<string, Array<Person>> = m2;
// As a Map, it appears that value cannot be a generic object
let m4: Map<string, Doctor> = new Map<string, Doctor> ();
m4.set('bob', d);
let m5: Map<string, Person> = m4;
It errors with the following statement
28: let m5: Map<string, Person> = m4;
^ Cannot assign `m4` to `m5` because property `license` is missing in `Person` [1] but exists in `Doctor` [2] in type argument `V` [3]. [prop-missing]
Flow
This is failing because it would be valid to do
m5.set("foo", { name: "foo" });
since that is a valid Person and that would corrupt m4 since it no longer contains Doctor objects.
For your code to work, you m5 needs to be read-only, and m3 needs to be read-only with read-only arrays, e.g.
let m3: $ReadOnlyMap<string, $ReadOnlyArray<Person>> = m2;
and
let m5: $ReadOnlyMap<string, Person> = m4;
(Flow Try)
I am trying to load all of my product data from firestore. I have a data schema:
class SingleProduct with ChangeNotifier {
static const EVENT = "event";
static const IMGURL = "imgUrl";
static const NAME = "name";
static const PRICE = "price";
//Private Variables
String _event;
String _imgUrl;
String _name;
double _price;
// getters
String get event => _event;
String get imgUrl => _imgUrl;
String get name => _name;
double get price => _price;
SingleProduct.fromSnapshot(DocumentSnapshot snapshot) {
_event = snapshot.data()[EVENT];
_imgUrl = snapshot.data()[IMGURL];
_name = snapshot.data()[NAME];
_price = snapshot.data()[PRICE];}
}
I have then created a class to map all the data received to a list of products:
class ProductServices {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
String collection = 'products';
Future<List<SingleProduct>> getAllProducts() async =>
_firestore.collection(collection).get().then((snap) {
print(snap.docs.length); // returns 11 products as expected
List<SingleProduct> allProducts = [];
snap.docs.map((snapshot) =>
allProducts.add(SingleProduct.fromSnapshot(snapshot)));
print(allProducts.length); //returns 0 so I think my map isn't working
return allProducts;
});
}
The Firestore query returns 11 query snapshots as expected but I then try and add them to a list using my product schema but the the results show the list has 0 elements. Any suggestions how to map the results of my fire base query to a list?
Use forEach():
snap.docs.forEach((snapshot) => allProducts.add(SingleProduct.fromSnapshot(snapshot)));
Explanation time: [tl;dr Your lambda wasn't executed at all]
map() is a lazy function intended for transforming elements in a list, which works only if the result is going to be used.
In your code, the result of map() is nowhere used later (eg. assigned to a variable), which is why the lambda within it is not called (why would it transform if the transformation is not used ahead?)
Also, it's not apt for your use-case.
To demonstrate its laziness, try running this code in DartPad:
void main() {
List<String> list = ["a", "b", "c"];
List<String> anotherList = [];
var mappingOutput = list.map((element) { anotherList.add(element); return element + "X"; }).toList();
print(list);
print(mappingOutput);
print(anotherList);
}
Notice that the result of map() is to be given back to a variable mandatorily, which pushes the laziness aside and executes it.
anotherList will be filled.
I've run into an error I can't quite figure out. I'm calling the service from the action and setting a new redux state with th response. However, I get the following error:
Error:
The argument type 'List<Chat> (C:\flutter\bin\cache\pkg\sky_engine\lib\core\list.dart)' can't be assigned to the parameter type 'List<Chat> (C:\flutter\bin\cache\pkg\sky_engine\lib\core\list.dart)'.
Action:
class GetChatRequest {}
class GetChatSuccess {
final List<Chat> history;
GetChatSuccess(this.history);
}
class GetChatFailure {
final String error;
GetChatFailure(this.error);
}
final Function getLastChatMessages = () {
return (Store<AppState> store) {
var chatService = new ChatService(store);
store.dispatch(new GetChatRequest());
chatService.getLast50Messages().then((history) {
store.dispatch(new GetChatSuccess(history));
});
};
};
Service:
Future<List<Chat>> getLast50Messages() async {
final response = await webClient.get('xxxx');
return response['data'].map<Chat>((e) => new Chat.fromJSON(e)).toList();
}
Change
store.dispatch(new GetChatSuccess(history));
to
store.dispatch(new GetChatSuccess(List<Chat>.from(history)));
to get a properly typed list.
history is a List<dynamic> that contains only Chat elements, but the list has still the generic type dynamic. To create a properly typed List<Chat> create a new list with that type and fill it with the elements from history.
See also https://api.dartlang.org/stable/2.1.0/dart-core/List/List.from.html
I am trying to figure out why I am getting the following exception when Im mocking my very simple interface.
System.Reflection.TargetParameterCountException: Parameter count
mismatch.
var zoneLocator = new Mock<IZoneLocator<ZoneInfo>>();
zoneLocator
.Setup(zl => zl.GetZoneInfo(
It.IsAny<double>(), It.IsAny<double>()))
.Returns((ZoneInfo zoneInfo) =>
Task.FromResult(zoneInfo));
var z = zoneLocator.Object.GetZoneInfo(1, 1);
interface:
public interface IZoneLocator<T>
{
Task<T> GetZoneInfo(double latitude, double longitude);
}
The overload of Returns that expects a Func is expecting a function that has the same inputs as the inputs of your mocked method. This allows you to alter the return value based on the inputs to the method.
So, to fix this, change your setup to this:
zoneLocator
.Setup(zl => zl.GetZoneInfo(It.IsAny<double>(), It.IsAny<double>()))
.Returns((double latitude, double longitude) =>
Task.FromResult(/* TODO: create a timezone somehow*/));