EF Like operator usage - c#

I tried to use DbFunctions.Like with EF 6.2 and got run-time error:
LINQ to Entities does not recognize the method 'Boolean
Like(System.String, System.String)' method, and this method cannot be
translated into a store expression.
Code:
list=list.Where(p=> DbFunctions.Like(p.Master_Bill,"somestring%")); where list is IQueryable<SomeView>
It compiles OK. I thought it can be used with EF 6.2. I know there is also EF Core, did not look at it
Any ideas?
thanks

What about
list = list.Where( p => p.Master_Bill.StartsWith(someString));
?
If the user can enter a wildcard(s) in their search string you can evaluate the string for legal combinations I.e. "?somestring", "somestring?" or "?somestring?" then choose the appropriate where condition.
wildcardResult = evaluateWildcard(someString);
switch(wildcardResult.Result)
{
case WildcardResult.NoWildcard:
list = list.Where( p => p.Master_Bill == wildcardResult.SearchString);
break;
case WildcardResult.StartsWith:
list = list.Where( p => p.Master_Bill.StartsWith(wildcardResult.SearchString));
break;
case WildcardResult.EndsWith:
list = list.Where( p => p.Master_Bill.EndsWith(wildcardResult.SearchString));
break;
case WildcardResult.Contains:
list = list.Where( p => p.Master_Bill.Contains(wildcardResult.SearchString));
break;
}
Where the result class contains an enum for the detected search expression pattern, and the search expression with the wildcard characters stripped to use as the SearchString.
It would also be advisable to evaluate the length of the search string for a minimum viable length when using wildcards. Users could trigger rather expensive queries by using expressions like "?" or "?e?".
Edit: DbFunctions.Like does work as well with SQL Server. Any error you are getting is likely due to an assumption about the IQueryable you are running or the field you are comparing. (I.e. not a mapped column, or a particular data type?)
For instance: Something like this works just fine..
var data = _context.People.Where(p => DbFunctions.Like(p.Name, "s%")).ToList();
Which would return all People with a Name starting with "S". (case insensitive)
I'd look at what your entire IQueryable looks like, as well as that Master_Bill is both a mapped column and a regular NVARCHAR/VARCHAR column.

Related

How can I issue a LINQ query to a SQL Server to check if a column value starts with a word?

I have this already
if (options.English != null) query = query
.Where(w => w.English.Contains(options.English));
What I would like to do is to extend this (maybe with another if clause) to make it so that if:
a user enters ^abc then my query would check if the word starts with "abc".
a user abc then it would check if the column contains "abc"
I am using a SQL Server back-end database. Could anyone give me a suggestion as to how I could implement this functionality.
Assuming that you're using Entity Framework, you can use the StartsWith() and EndsWith() methods, to achieve the same results as Contains() except only at the beginning or the end of a string. It will generate the code for you.
Then simply create conditional statements in your code, in order to determine which one of the methods you should use.
Some word of advice:
There might be a bug with EF Core, in which it turns StartsWith("string") into LIKE "string%" which might yield incorrect results with strings, containing wildcard characters such as "_".
So I'd advise you to use plain SQL with EF Core, and given that you're using SQL Server as a DBMS, query like that:
if (searchText.StartsWith("^"))
{
var result = query.FromSql($"SELECT something FROM table WHERE PATINDEX({searchText.Substring(1)}, something) = 1");
}
else
{
var result = query.FromSql($"SELECT * FROM table WHERE PATINDEX({searchText.Substring(1)}, something ) <> 0");
}
With PATINDEX() you will get correct results even if your pattern string contains wildcard characters - escaping potential bugs with relying on StartsWith() and EndsWith() to generate proper SQL code.
But that's only for EF Core, EF 6 works like a charm the way other people answered :)
You can put that choice in a conditional statement:
IQueryable<Whatever> query = ...;
if (searchText.StartsWith("^"))
{
query = query.Where(w => w.English.StartsWith(searchText.Substring(1)));
}
else
{
query = query.Where(w => w.English.Contains(searchText));
}
You can also do the same comparison inline, but that'll generate very ugly SQL, if it even works:
query = query.Where(w =>
searchText.StartsWith("^")
? w.English.StartsWith(searchText.Substring(1))
: w.English.Contains(searchText));
Do note that you generally don't want to search text using SQL, as that results in a pretty poor user experience. Take a look at full-text indexing.
if (options.English != null)
{
bool englishStartsWith = options.English.StartsWith("^");
if(englishStartsWith)
{
query = query.Where(w => w.English.StartsWith(options.English.Substring(1)));
}
else
{
query = query.Where(w => w.English.Contains(options.English));
}
}

error in calling and calculate function with int? as input

I have a function like that :
public int? calculateContractPrice(int? comid)
{
int? sum = 0;
var q = from i in dbconnect.tblMaterialGroups
where i.tenderId == _tenderId
select i.id;
foreach (int i in q )
{
var q2 = from g in dbconnect.tblMaterialTenderAnnouncePrices
where g.MaterialGroupId == i && g.companyId == comid
select g;
sum = q2.First().amount*q2.First().price + q2.First().amount*q2.First().PriceForElse + sum;
}
return sum ;
}
When i try to execute this :
List<presentationcontract> q = (from i in dbconnect.tblContracts
where i.tender == _tenderId
select new presentationcontract()
{
tax =(calculateContractPrice(i.companyId)*(6/100)).ToString()
}).ToList();
Tax is string .after executing i got this error :
couldn't translate expression calculateContractPrice(i.companyId)*(6/100),invoke(value(system.Func1[system.nullable1[system.Int32]]))).ToString() into SQL and could not treat it as a local expression
Your edit makes clear the issue. You're trying to do
tax =(calculateContractPrice(i.companyId)*(6/100)).ToString()
in a sql statement but calculateContractPrice is in c#! To understand what's going on you really need to understand a bit how LINQ works.
First of all, stop using the silly sql-style syntax for LINQ. It is less powerful than the lambda syntax and hides what is going on under the hood in a way that makes it hard to understand.
Second consider a LINQ statement
users.Where(u => u.Name == "George").ToList();
where users is IEnumerable<User>. What happens here is that the lambda part is of type Func<User, bool> and gets compiled to a method that gets run against every single instance of User.
Now consider this LINQ statement
db.Users.Where(u => u.Name == "George").ToList();
where db.Users is IQueryable<T>. This looks the same but what happens is VERY different. What happens is that lambda is actually of type Expression<Func<User, bool>> this doesn't get compiled to a method, instead it gets compiled into something called an expression tree. This gets passed to the LINQ provider (in your case Entity Framework I'm guessing) which examines it and converts that into a SQL statement
SELECT Id, Name, Address FROM users WHERE Name = 'George'
What is happening in your case is that it sees the call to calculateContractPrice and simply has no way of converting that to SQL.
What you should therefore do is ensure the query runs first, then use the IEnumerable<T> form of LINQ that runs in c# to call your method.
var contracts = dbconnect.tblContracts.Where(i => i.tender == _tenderId)
.ToList() //Query executes here, now you have IEnumerable<T>
.Select(i => new PresentationContract {
Tax = ...
}).ToList(); //this ToList is only necessary if you want to prevent multiple iteration
You will want to solve all the other problems everyone else pointed out as well of course.
A few other notes - you will want to read up on .Net naming conventions. Usually, anything public,protected, or internal (classes, fields, properties, etc.) is recommended to be PascalCase. Also you probably want to move the division portion into the PresentationContract class. This class that has a Tax property should probably be the one that knows how to generate it.
Try this:
int? ret = calculateContractPrice(i.companyId);
if(ret.HasValue)
{
tax =(ret.Value*(6/100)).ToString();
}
You should make sure that the function indeed returned a value and then you use that integer value in calculation.

Linq to entities - Lambda - Concatenate strings

I would like to concatenate a string using lambda to compare that concatenated value against a certain condition.
Invoices = Invoices.Where(f => ((string)f.invoice_prefix + String.Format("{0:0000}", Convert.ToInt32(f.invoice_number))).ToLower().Equals(condition7));
But I get an error message :
The name 'f' does not exist in the current context
Tried several String.Format and String.Concat variants like
Invoices = Invoices.Where(f => (String.Format("{0}{1}",f.invoice_prefix,String.Format("{0:0000}", Convert.ToInt32(f.invoice_number)))).ToLower().Equals(condition7));
but no success... Can somebody help me with the syntax?
Thanks in advance!
Linq to Entities doesn't understand all of the .NET framework methods.
In order to run this as a SQL statement on the database, you need to only use operators that can be converted to SQL. That means you need to re-write your predicate using primitive data types.
So something like this:
string prefixCondition = ...
int invoiceNumberCondition = ...
Invoices.Where( f =>
f.invoice_prefix == prefixCondition
&&
f.invoice_number == invoiceNumberCondition
)
I recommend using LinqPad to test with, as it shows you the generated SQL statement.

How to reuse where clauses in Linq To Sql queries

I have users searching records of type Record. They type a search term in a textbox and then I search records by matching several fields with the search term.
My query looks like:
var results = from record in DataContext.Records
where
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
select record;
I have a number of queries that all use the same filter and thus I would like to extract the filtering so it can be reused. Something like:
var filter = new Func<Record, string, bool>(
(record, term) =>
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
);
var results = from record in DataContext.Records
where filter(record, term)
select record;
However, it does not work because:
Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.
How can I reuse my where condition across queries?
You need to build an expression instead of a function:
Expression<Func<Record, bool>> filter =
record => record.Field1.ToLower().Contains(term); // rest omitted
The lambda expression remains the same, but you need to return it into a variable of type Expression<Func<Record, bool>> -- that will make the C# compiler compile it as an expression instead of a delegate, allowing it to be passed to LINQ to SQL.
However, you won't be able to use an expression variable with a C#-syntax where clause: you'll need to use the Where extension method:
var results = DataContext.Records.Where(filter);
Edited to add: If you want to be able to create filters on different terms, you just need a method to produce an expression from a term:
private static Expression<Func<Record, bool>> Filter(string term)
{
return r => r.Field1.ToLower().Contains(term);
}
var results = DataContext.Records.Where(Filter(term));
If you prefer to keep filter as a lambda as you have at the moment, you can do so, but the generics get a bit nested:
Func<string, Expression<Func<Record, bool>>> filter =
term => (r => r.Field1.ToLower().Contains(term));
var results = DataContext.Records.Where(filter(term));
Regardless, the important thing is that what goes in the Where clause must be an Expression<Func<Record, bool>> -- but as shown above you can make the expression depend on term by building a suitable expression on the fly. Which is exactly what LINQ to SQL would be doing if you spelled out the filter longhand in the Where clause.
Use a CompiledQuery!
var filter = CompiledQuery.Compile(
(DatabaseDataContext dc, Record record, string term) =>
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
);
var results = from record in DataContext.Records
where filter(DataContext, record, term)
select record;
For more information, see How to: Store and Reuse Queries.
In addition to the Expression<Func<Record, bool>> issue that others have pointed out, I suggest looking into PredicateBuilder. It's very good for dynamically combining lambda expressions.
I think you need to make it an Expression<Func<Record, bool>>. Otherwise it's trying to translate the actual C# method call to SQL rather than the description of it. This is not a guarantee that this version will work; I'm not sure which string functions are translatable to SQL.

Linq To Sql - Changing Sort Order At Run-Time with well known static typing

This is not another question about 'How Can I Sort Dynamically (based on an arbitrary user provided field)?'
The question is -- how can I change sort order when I know the potential sorts in advance? (And thus avoid reflection / custom Expression building typically associated with truly dynamic sorting.)
Take for instance this subquery (shortened for this example) of a larger query:
(from solutionIds in context.csExtendedQAIncident_Docs
where solutionIds.tiRecordStatus == 1
&& (from solutionProductAssocation in context.csProductDocs
where solutionProductAssocation.iSiteId == Settings.Current.WebUtility().Onyx.SiteId
&& (from allowedProduct in context.KB_User_Allowed_Products
where allowedProduct.UserId == userId
select allowedProduct.ModelCode
).Contains(solutionProductAssocation.chModelCd)
select solutionProductAssocation.chIdNo).Distinct().Contains(solutionIds.chIdNo)
).OrderByDescending(s => s.dtUpdateDate)
.Select(s => s.chIdNo)
.Take(count ?? Settings.Current.WCFServices().Output.HomePage.MaxRows)
The OrderByDescending portion works as I would expect.
Now -- I want to factor that out like the following:
Expression<Func<csExtendedQAIncident_Doc, IComparable>> ordering = (s) => s.dtUpdateDate;
if (viewType == HomepageViewType.MostViewed)
ordering = (s) => s.vchUserField8;
else if (viewType == HomepageViewType.MostEffective)
ordering = (s) => s.vchUserField4;
and then use:
OrderByDescending(ordering)
This does compile, but blows up at run-time.
Unsupported overload used for query operator 'OrderByDescending'.
This of course comes from deep in the bowels of System.Data.Linq.SqlClient.QueryConverter -- in particular VisitSequenceOperatorCall. Reflectoring that code reveals that the following conditions must be met for OrderByDescending to properly evaluate. 'mc' is the MethodCallExpression passed into the method.
if (((mc.Arguments.Count != 2) || !this.IsLambda(mc.Arguments[1]))
|| (this.GetLambda(mc.Arguments[1]).Parameters.Count != 1))
{
break;
}
So essentially that MethodCallExpression has to have 2 arguments, the second of which has to be a Expressions.LambdaExpression with a single parameter (presumably the sort field). If that code breaks out, the exception that I got is thrown.
So clearly I have not constructed the expression correctly. Without digging in any further here, does anyone know how to correctly construct the sorting Expression?
I think the unsupported part of your code is the use of IComparable as a general return type for your ordering expression. If you consider the plain use of OrderByDescending, the compiler-generated lambda expression has a return type of the type of the property that you're ordering by: for example, an Expression<Func<csExtendedQAIncident_doc, string>> for a string property.
One possible answer, although I'm not sure whether it works in your case, is to first create an unordered query:
IQueryable<Foo> unorderedQuery = from f in db.Foo select f;
And then, depending on the sort:
IOrderedQueryable<Foo> orderedQuery = unorderedQuery
.OrderBy(f => f.DefaultSortKey);
if (sortBy == SortByName)
orderedQuery = unorderedQuery.OrderBy(f => f.Name);
else if (sortBy == SortByDate)
orderedQuery = unorderedQuery.OrderBy(f => f.Date);
// etc.
I believe that this will not work unless the two possible fields have the identical type.
Then the linq to sql will (if possible) correctly create the relevant sql.
so for example if both of those fields were DateTimes:
Expression<Func<csExtendedQAIncident_Doc, DateTime>> ordering =
s => s.dtUpdateDate;
if (viewType == HomepageViewType.MostViewed)
ordering = (s) => s.vchUserField8; // a DateTime
else if (viewType == HomepageViewType.MostEffective)
ordering = (s) => s.vchUserField4; // another DateTime
Then this would work just fine (I tested it and it worked)
You could instead do a per type order by either a series of nested switch/if statements of by constructing a dictionary or similar structure to get them.
For the linq to sql to work without explicit dynamic creation of the query I believe it must know the precise type of the query as opposed to just it being an IComparable...

Categories

Resources