LINQ Queryable.Take() with range parameter could not be translated - c#

With EF Core 7.0, when I query data with basic pagination:
var data = (from item in _db.items
where... // filtering
orderby.... // ordering
select item)
var dataToSend = await data.Skip(x).Take(Y).ToListAsync();
Everything works as expected.
However, when I try using Queryable.Take(x..y) "with the range parameter", for example:
var from = x;
var to = y;
var dataToSend = await data.Take(from..to).ToListAsync();
I receive an error that the LINQ expression could not be translated.
Why isn't this working?

Why isn't this working?
.NET 6 has introduced several new LINQ methods (and overloads to existing ones) which require their support to be implemented in every LINQ provider otherwise they will not work correctly. ATM EF Core team is considering/working on supporting them - you can track this summary issue or this one, particularly covering Take(Range) (and ElementAt(Index)).

The Take method in LINQ expects an integer argument that represents the number of elements to take.
It also can accept a Range parameter, in the form of a Range object.
A small example:
using System;
using System.Linq;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var result = numbers.AsQueryable().Take(2..4).ToList();
Console.WriteLine(string.Join(", ", result));
}
}
}
Try adding the AsQueryable() before the Take() method.
The Take method in LINQ is equivalent to the SQL SELECT TOP clause, which also expects a single integer argument.

Related

Parameterized Where IN clause does not work with CosmosClient QueryDefinition Object

I am trying to write a parameterized query that has IN clause.
For Ex :
Working code
Input string : "'guid1','guid2','guid3'"
public List<Employee> GetEmployeeIds(string ids){
QueryDefinition query =new QueryDefinition(#"Select * from Employee where Employee.Id in ("+ ids+")");
var result = GetDetails(query,cosmosClient);
return result;
}
Result: It returns the expected result
Non-working code
Input string : "'guid1','guid2','guid3'"
public List<Employee> GetEmployeeIds(string ids){
QueryDefinition query =new QueryDefinition(#"Select * from Employee where Employee.Id in ( #ids )")
.WithParameter("#ids", ids);
var result = GetDetails(query,cosmosClient);
return result;
}
Result: It returns 0
NuGet package used for above code: Microsoft.Azure.Cosmos 3.8.0
Note: I have tried all the options which are mentioned in this link but it does not work with CosmosClient QueryDefinition Object
WHERE IN with Azure DocumentDB (CosmosDB) .Net SDK
Any help on this is highly appreciated.
Thanks in advance.!!
I'm guessing that your ids value is something like "12,42,94,7". As a string parameter #ids, the expression in (#ids) is broadly the same as in ('12,42,94,7'), which won't match any values, if the values are the individual numbers 12, 42, 94 and 7. When you used the simple contatenated version, the meaning was different - i.e. in (12,42,94,7) (note the lack of quotes), which is 4 integer values, not 1 string value.
Basically: when parameterizing this, you would need to either
use multiple parameters, one per value, i.e. ending up with in (#ids0, #ids1, #ids2, #ids3) with 4 parameter values (either by splitting the string in the C# code, or using a different parameter type - perhaps params int[] ids)
use a function like the STRING_SPLIT SQL Server function, if similar exists for CosmosDB - i.e. in (select value from STRING_SPLIT(#ids,','))
This is working for me, where #ids is an array type:
List<long> ids = new List<long>();
ids.Add(712300002201);
ids.Add(712300002234);
string querySql = #"Select * from Employee where ARRAY_CONTAINS(#ids, Employee.Id )";
QueryDefinition definition = new QueryDefinition(query).WithParameter("#ids", ids);
Have you tried looking into the question asked below.
Azure Cosmos DB SQL API QueryDefinition multiple parameters for WHERE IN
I think ids are being treated as a single string therefore the results are not returning.
Alternatively, you could try using Microsoft.Azure.DocumentDB.Core package and make use of Document Client to write LINQ queries like in the code snippet below.
using (var client = new DocumentClient(new Uri(CosmosDbEndpoint), PrimaryKeyCosmosDB)){
List<MyClass> obj= client.CreateDocumentQuery<List<MyClass>>(UriFactory.CreateDocumentCollectionUri(databaseName, collectionName))
.Where(r => ids.Contains(r.id))
.AsEnumerable();}

How can an instance of an anonymous type's property of a simple type not always return the same value? [duplicate]

This question already has answers here:
C# Select query not modifying external variable
(3 answers)
How to update a global variable inside `where` clause in LINQ?
(2 answers)
Execution flow of a linq query
(5 answers)
When is the LINQ query actually get executed?
(3 answers)
Closed 2 years ago.
I don't really know how to frame this question, but I was really puzzled when I saw this behaviour. Is it really supposed to be like this?
var data = new List<int> { 2, 4, 1 };//some dummy data for this example
var ii = 1;
var dataWithIds = data.Select(x => new
{
id = ii++,
value = x
});//I thought ii now would be 4, but it's still 1
var firstId = dataWithIds.First().id;//== 1, as expected. Now ii is 2
var alsoFirstId = dataWithIds.First().id;//== 2, not expected. Now ii is 3
ii = 1000;
var okMaybeItDoesNotWorkAsAnIdThen = dataWithIds.First().id;//==1000. Now ii is 1001
With new {id = ii++} I thought the id property always return the value ii had at declaration/instantiation (and then ii is incremented at declaration/instantiation), but it seems like this actually returns the value of ii at the time the property is called (and then ii is incremented when the property is called).
My intention was to create a list of some data (or really an IEnumerable of an anonymous type, and of course a bit more than in this example code), including an (auto-incrementing) ID.
I have ways around this (for example just adding .ToList()), but it would be interesting knowing why it works like this. It also took some time to find out that this was not working like I intended (and how it was working), so hopefully this can be of help to others.
You have to understand how LINQ works:
var data = new List<int> { 2, 4, 1 };//some dummy data for this example
var ii = 1;
var dataWithIds = data.Select(x => new
{
id = ii++,
value = x
}); // Nothing is executed at this point. Only an expression tree is created in memory.
var firstId = dataWithIds.First().id; // The First() executes the ii++
var alsoFirstId = dataWithIds.First().id; // The First() executes the ii++
I know it can be puzzling but it really is a beautiful construct. You can delay execution of code until all needed execution is defined. That is especially effective when querying databases. You can postpone execution until all filters, joins, whatever have been declared. At the point of execution .ToList(), .First(), etc. the request is sent to the database as one sometimes big query.
You can use Select overload with additional index parameter for such case.
var dataWithIds = data.Select((x, i) => new
{
id = i + 1,
value = x
});
According your question. If local variable is defined outside LINQ query it will be automatically wrapped by compiler into hidden class with property ii and instance of this class will be reused after every First, Count, ToList, etc. (functions which enumerates). That's why you see "strange" results. It is called Closures and there are a lot of explanations What are 'closures' in .NET?

EF - finding items which are not in the list of integers - resulting query doesn't use parameters

I am using the following method:
public PagedResult<PaymentPlanItems> GetPagedRequest(SearchRequest searchRequest, List<int> idsToExclude)
{
var list = _dbSetList.AsQueryable();
if (idsToExclude != null)
{
list = list.Where(item => !idsToExclude.Contains(item.ItemId));
}
var query = SearchHelper.GetFilteredSearch<PaymentPlanItems>(list, searchRequest);
var pagedResultMessage = SearchHelper.GetPagedResult(query, searchRequest);
return pagedResultMessage;
}
where idsToExclude I originally got using this (found from another SO thread):
if (!string.IsNullOrEmpty(searchViewModel.ItemsToExclude))
{
List<int> idsToExclude = new List<int>(Array.ConvertAll(searchViewModel.ItemsToExclude.Split(','), int.Parse));
searchViewModel.Result = _paymentPlanItemsAdapter.GetPagedRequest(searchViewModel.SearchRequest, idsToExclude);
}
I then look at the generated query using profile and I see that I get the following as part of my query:
WHERE ( NOT ([Extent1].[ItemId] IN (440, 1017)))
I don't know if I should be really concerned as my numbers get inserted into the query directly and not as parameters and if I should be, what modifications to this method / approach I should take to make this query use parameters?

C# Entity Framework text SQL query wrapper

I have a problem similar to this:
How to retrieve multiple columns from non-entity type sql query?
I need to implement the method string[,] DirectQuery(string sqlText, string[] param) which is basically a C# equivalent of SQL Server Management Studio.
The user is supposed to enter a SQL query as string text (+ string parameters to avoid SQL injection) and receive back a string matrix containing the outcome of the query.
Internally, I'm using Entity Framework.
Here's my implementation:
public string[,] DirectQuery(string sqlQuery, string[] param)
{
//discover how many fields are specified in the select clause
string ip = sqlQuery.ToLower().Split(new string[] { "from" }, StringSplitOptions.None)[0];
int cols = ip.Count(y => y == ',') + 1;
//execute the query
DbRawSqlQuery<string> res = param != null ? _context.Database.SqlQuery<string>(sqlQuery, param) : _context.Database.SqlQuery<string>(sqlQuery);
//wrap everything in a matrix to return
return res.ToArray().Array2Matrix(res.ToArray().Length /cols, cols);
}
where
public static T[,] Array2Matrix<T>(this T[] flat, int rows, int cols) where T : class
is my custom method that turns flat arrays into rows x cols matrices.
If in the select clause users specify a single attribute, that works fine, but in case of 2+ fields needed the execution of DirectQuery fires the runtime exception dbrawsqlquery he data reader has more than one field. Multiple fields are not valid for EDM primitive or enumeration types. That's completely reasonable, but since the query can be whatever I can't create a custom class to wrap every possible outcome.
What do you suggest?
The problem is that you're using a method - DbRawSqlQuery which must be told what type to expect, and you're telling it to expect just a string, so it has no idea what to do with more than one returned column.
Maybe it would work if you specified string[] or IEnumerable<string> or something? Alternatively you could define a series of objects with 1, 2, 3, 4 etc values and detect the number of items at runtime and use the correct class... but that seems absurd.
Really though, I'd suggest NOT using EF as someone suggested above. Find something which can return dynmamic objects, OR just use ADO.Net directly.

c# Lambda and grouping

trying to get my head around using Lambda expressions to fetch data from my database.
Say I have a table that looks a bit like this (notice the spaces and casing):
name, count:
iPhone 4, 15
iphone 4, 2
iPhone4, 8
If I try to find items by name (using StartsWith()), I only want to fetch the result with the highest count, independent of casing and spaces. So searches for "iphone4" "i p h o n e 4", "iPhone4" sholud all return the "iPhone 4"-record
If you have a MS Sql Server 2005+ the following would work for your stated example:
var inputString = "iPhone 4";
var token = inputString.ToLower().Replace(" ", "");
var tokenizedQuery = DataContext.Devices.Select(d => new { Device = d, Token = d.Name.ToLower().Replace(" ", "") });
var filteredQuery = tokenizedQuery.Where(d => d.Token == token);
var resultsQuery = filteredQuery.Select(d => d.Device).OrderByDescending(d => d.Count);
var result = resultsQuery.FirstOrDefault();
Here is what is going on:
You are creating a tokenized version of your input string by lower-casing it and then removing spaces.
Then you are creating a pseudo-column on your table to create a similar token column
Filter your results based on this token
Finally, select only the record with the highest count
However it is very important that you realize the ToLower() and Replace() methods are being translated to T-SQL commands that run on the sql server and not in your app. This means should you need more sophisticated tokenizing routines, or you are not using MS SQL this may not work!
As others have noted, you may want to clean up your design somewhat. You are essentially storing a key or search keyword that can have many permutations. Doing the tokenizing in a query is not portable or performant, so you should ideally store the tokenized version of this string in its own column. Alternatively, look into Full Text Indexes, as they may also address your problem (again, if using MSSQL).
Let's assume that you have a Collapse string extension, which is not hard to write. One thing you'll note is that there won't be a mapping from this to SQL so the final filtering will have to be done in LINQ to Objects. You might be able to make the DB query more efficient by doing partial filtering (i.e., on iphone), then complete the filtering in memory.
db.Table.ToList().Where( t => t.Name.Collapse().StartsWith( searchString.Collapse() )
.OrderByDescending( t => t.Count )
.Take( 1 );
Where Collapse is
public static class StringExtensions
{
public static string Collapse( this string source )
{
if (string.IsNullOrWhiteSpace( source ))
{
return string.Empty;
}
var builder = new StringBuilder();
foreach (char c in source)
{
if (!char.IsWhiteSpace( c ))
{
builder.Append( c );
}
}
return builder.ToString();
}
}
Note: you'd be better off sanitizing your database if possible AND you really want these to map to the same thing.

Categories

Resources