I would like to retrieve a list of values from a SQL table where the records start with a prefix defined in another table.
This post gives an accurate answer, but it is for EF and not Linq to SQL.
With SQL I get an error:
Only arguments that can be evaluated on the client are supported for
the String.Contains method
Sample code:
var lookupList = dc.LookupTable.Select(p => p.Prefix);
var q = dc.Personnel
.Where(item => lookupList
.Any(p => item.Surname.StartsWith(p))).Select(x => x.PersonID);
This works with EF. Yes, I can ToList() my collections but the tables are big and the query becomes very slow. Any suggestions on how to make it work without enumerating my objects?
This part: .Any(p => item.Surname.StartsWith(p)) gives the error:
Only arguments that can be evaluated on the client are supported for the String.Contains method
It tells you Contains method does not work with the given parameter which can only be evaluated on the server. StartsWith basically uses the same mechanism.
So, instead of Contains or StartsWith you should use IndexOf to find out whether or not the containing parameter is occured at the beginning or not:
.Any(p => item.Surname.IndexOf(p) == 0)
According to MSDN:
IndexOf(T):
The index of item if found in the list; otherwise, -1.
This answer is partially taken from here.
Related
"fullNames" is a list of strings that I want to search for in a database with the 2 tables "FistName" and "LastName". But this code returns the error: "Unable to determine the serialization information for u => (u.FirstName + u.LastName)."
var filter = builder.In(u => u.FirstName + u.LastName, fullNames);
var task = _db.GetCollection<Models.User>(Models.CosmosDb.Collections.Users)
.Find(filter)
.ToListAsync();
var users = await _asyncRetryPolicy.ExecuteAsync(() => task);
Tried to replace the first line with this, but with the same result:
var filter = builder.In(u => $"{u.FirstName}{u.LastName}", fullNames);
What am I doing wrong?
The driver expects a lambda expression that evaluates to a simple property as parameter for In. In the sample, the lambda expression concatenates two properties, hence the error.
Instead of using a Find, you could use Aggregate. This offers more options when querying and reshaping the data.
In this sample, the first stage would be a $set stage that concatenates the first and last name of the document. In a subsequent $match stage, you can include the $in condition and filter the data.
From a performance perspective, it would be best if you store the full name with the documents. Then you only need a simple filter that can also use an index.
I've searched through numerous examples on how to filter collection based on another collection and I have found an easy way, like so:
var oShipnotes = await DbContext.ShipNotes.Where(s => oManageRouteDto.ShipNotes.Any(mr => mr.Id == s.Id)).ToListAsync();
however it throws an exception that says it cannot be translated to SQL query.
Could anyone point me the right direction how to solve this?
Thanks!
Replace nested LINQ query to materialized list of identifiers:
// 1) get the list of target ship note identifiers
var ids = oManageRouteDto.ShipNotes.Select(mr => mr.Id).ToList();
// 2) pass this list into Where using Contains
var oShipnotes = await DbContext.ShipNotes.Where(s => ids.Contains(s.Id)).ToListAsync();
EF is aware of this pattern and translates IList<T>.Contains into SQL's IN condition.
Since EF deals with IQueryables, each LINQ query must be translated into valid SQL expression. As a result, EF and underlying provider cannot translate every valid LINQ query (from C# perspective) just because SQL is not C#.
I have this code and it gives me this following error "LINQ to Entities does not recognize the method"
var AuxiliarValue = _context.company.LastOrDefault(x => x.StartValue.HasValue && (x.StartValue.Value < InicialValue));
InicialValue is Double
But when I put a ToList(), it works
var AuxiliarValue = _context.company.ToList().LastOrDefault(x => x.StartValue.HasValue && (x.StartValue.Value < InicialValue));
Can anyone explain to me why it works with ToList()?
LastOrDefault is not supported with LINQ to Entities. You can use OrderByDescending and then use FirstOrDefault
var AuxiliarValue = _context.company
.OrderByDescending(r=> yourFieldtoOrder)
.FirstOrDefault(x => x.StartValue.HasValue && (x.StartValue.Value < InicialValue));
The reason it works with ToList is that ToList will iterate all the results and bring them in memory, so the LastOrDefault is executed on an in-memory collection, rather than at the database end.
It works when you add ToList because the query is no longer being translated into SQL and executed by the database. Instead, the entire table of data is returned from the database to your application, a List is built to hold that data, and then the operation is performed using LINQ to Objects.
You probably don't want to do that; you probably want to adjust the way that you query the data such that it can be translated into SQL and run against the database.
Is that possible in LINQ to write a nice one-liner to get a first matched element or if there's no match than get first element in the collection?
E.g. you have a collection of parrots and you want yellow parrot but if there's no yellow parrots - then any will do, something like this:
Parrots.MatchedOrFirst(x => x.Yellow == true)
I'm trying to avoid double-go to SQL Server and the ORM we use in this particular case is Dapper.
What about:
var matchedOrFirst = Parrots.FirstOrDefault(x => x.Yellow == true)
?? Parrots.FirstOrDefault();
Edit
For structs, this should work:
var matchedOrFirst = Parrots.Any(x => x.Yellow == true)
? Parrots.First(x => x.Yellow == true)
: Parrots.FirstOrDefault();
Edit: It was a linq to SQL solution
First building a handy extension
public static T MatchedOrFirstOrDefault<T>(this IQueryable<T> collection, System.Linq.Expressions.Expression<Func<T, Boolean>> predicate)
{
return (from item in collection.Where(predicate) select item)
.Concat((from item in collection select item).Take(1))
.ToList() // Convert to query result
.FirstOrDefault();
}
Using the code
var matchedOrFirst = Parrots.MatchedOrFirstOrDefault(x => x.Yellow);
If you want to avoid a 2nd SQL call and since requires branching logic, its unlikely that Dapper will know how to convert a LINQ query you come up with into appropriate SQL IIF, CASE, or whatever other SQL-specific functions you end up using.
I recommend you write a simple stored procedure to do that and call it from Dapper.
Depending on its usage though, if this page only has one or two queries on it already, and is located reasonably close (latency wise) to the server, a 2nd simple SELECT won't hurt the overall application that much. Unless it is in a loop or something, or your example is trivial compared to the actual query regarding the cost of the first SELECT.
I want to order by my results with the matches count in my string line.
So here is code
.ThenByDescending(p => p.Title.ToLower()
.Split(' ')
.Count(w => words.Any(w.Contains)));
But it bring me error and says that LINQ can't parse Split into SQL.
LINQ to Entities does not recognize the method 'System.String[]
Split(Char[])' method, and this method cannot be translated into a
store expression.
How can I implement Split via LINQ?
For example, for this array it must order in this way
words = { "a", "ab" }
ab a ggaaag gh //3 matches
ba ab ggt //2 matches
dd //0 matches
it means that Linq to entities failed to find translation of split method that can be written as sql query. if you want to perform split functions you have to bring the record in memory by calling ToList(), AsEnumerable() etc.
var result = (from t in db.Table
select t).AsEnumerable().OrderBy(x=>x.Column).ThenByDescending(p=>p.Title.ToLower.Split(' ')....);
You will need to perform the sorting in LINQ to Objects because LINQ to Entities cannot translate the C# code into SQL (or the language of whatever DB you're using).
You can do it like this.
var results =
objectContext
.Where(a => a == b) //Whatever
.AsEnumerable()
.ThenByDescending(p=>p.Title.ToLower().Split(' ').Count(w=>words.Any(w.Contains)));
AsEnumerable() (along with ToArray() or ToList()) turn the LINQ to Entities back into LINQ to Objects.
One can't expect LINQ to Entities to be able to convert that to SQL.
The best solution is to change the schema such that each word in a post's title is stored as a separate row in a separate table (with the appropriate associations). The query can then use explicit (group) join operations or the FK association property.
If you can't do this and yet want the query to run on the database, you'll have to look into writing a user-defined function to work with delimited strings. Read this question for more info. This will be a lot of work though.
The easiest solution (if you can afford to do it) of course is to pull everything back to the client just use LINQ to Objects for that part of the query, as mentioned by #Muhammad Adeel Zahid.