EF Core expression tree FORMAT equivalent - c#

I'm trying to build dynamic SQL queries using expression trees. I'm having issues implementing SQL Function FORMAT on datetime datatype. I would like to generate expression that would translate to the following SQL statement:
SELECT *
FROM ComputerUpdate
WHERE FORMAT(UpdatePerformed, 'dd/MM/yyyy, hh-mm-ss') LIKE '%15/12/%'
I was able to dynamically generate LIKE part of SQL query, but I cannot find FORMAT equivalent.
I tried the following code:
var format_method = typeof(DateTime).GetMethod("ToString", new[] { typeof(string) });
return Expression.Call(
Expression.PropertyOrField(parameter, prop.Name),
format_method,
Expression.Constant("dd/MM/yyyy, hh-mm-ss", typeof(string))
);
However, this approach throws the following exception:
The LINQ expression 'DbSet().Where(o => o.UpdatePerformed.ToString("dd/MM/yyyy, hh-mm-ss").Contains("15/12/")' could not be translated. Additional information: Translation of method 'System.DateTime.ToString' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. 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.
Another downside of this approach is that it does not work on nullable DateTime properties - in that case, the following exception was thrown:
Method 'System.String ToString(System.String)' declared on type 'System.DateTime' cannot be called with instance of type 'System.Nullable`1[System.DateTime]'
I am using EF Core 7.0

Related

ExecuteUpdateAsync in EF Core 7.0: set property based on Logic throws InvalidOperation Exception

I am working on data intensive table that contains more than 100.000 records. I need to retrieve a column and update it via logic implemented in an extension method.
For example:
var updateResult = await _context.WebidPersons.ExecuteUpdateAsync(x => x.SetProperty(a => a.EmployeeInfo, x => x.EmployeeInfo.ReturnAsEncrypted());
The extension method is simply like that
public static string ReturnAsEncrypted(this string value)
{
// logic that encrypt the EmployeeInfo
}
The output is an exception
System.InvalidOperationException the expression could not be translated. Additional information: The following lambda argument to 'SetProperty' does not represent a valid property to be set: 'x => x.EmployeeInfo.ReturnAsEncrypted'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Source=Microsoft.EntityFrameworkCore.Relational
ExecuteUpdateAsync performs all work server-side (i.e. it is translated into something like UPDATE [t] SET [t].[EmployeeInfo] = ... FROM [Table] AS [t]), so EF Core needs to translate x.EmployeeInfo.ReturnAsEncrypted() into valid SQL. ReturnAsEncrypted seems to be some custom function which is not translated by default. One way is to look into mapping a method to a SQL function if it is possible to translate ReturnAsEncrypted into SQL. Otherwise you will need to fetch data and updated in on the client side using standard EF Core approach.

How to query SQL Server using DateOnly with ef core

I'm using .NET 6 and EF Core 6 with SQL Server and want to use the new DateOnly type.
I've been able to read and write data to the database using this converter however querying the table doesn't work because Linq doesn't know how to translate DateOnly.
Converter registration in DbContext:
protected override void ConfigureConventions
(ModelConfigurationBuilder builder)
{
builder.Properties<DateOnly>()
.HaveConversion<DateOnlyConverter>()
.HaveColumnType("date");
builder.Properties<DateOnly?>()
.HaveConversion<NullableDateOnlyConverter>()
.HaveColumnType("date");
}
Example
public XXXXByDateSpec(DateOnly validFrom)
{
Query.Where(x => x.ValidFrom.Year <= validFrom.Year);
}
But this results in the following exception.
System.InvalidOperationException: The LINQ expression 'DbSet().Where(c => c.ValidFrom.Year <= __validFrom_Year_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.
And when I'm trying to first parse it to a DateTime results in a similar error.
Query.Where(x => DateTime.Parse(x.ValidFrom.ToString()).Year <= DateTime.Parse(validFrom.ToString()).Year);
System.InvalidOperationException: The LINQ expression 'DbSet().Where(c => DateTime.Parse(c.ValidFrom.ToString()).Year <= __Parse_Year_0)' could not be translated. Additional information: Translation of method 'object.ToString' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information.
Translation of method 'object.ToString' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. 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.
How can I tell Linq to do the same like EF Core and before translating the code to SQL do a type conversion to DateTime? Is this possible?
Or how can I register the ToString() call to Linq? The links from the exception don't really help me out.
This should work, though not as readable. This benefits from using index if present. Also a contract with DateOnly as the parameter is confusing if only the year part is used.
public XXXXByDateSpec(int year)
{
Query.Where(x => x.ValidFrom < new DateOnly(year + 1, 1, 1);
}

Dynamic Linq gives error in combination with EntityFramework

We use Entity Framework, and we need some runtime build queries on our objects. Building expression trees from scratch seems like a lot of work, so we want to use "System.Linq.Dynamic"
Working through the samples I got this to work:
dbModel.As.Where("AStuff.Contains(#0) OR AStuff.Contains(#1)","ac","bc")
But if I try to build the expressions seperately like this:
Expression<Func<A, bool>> predicateA =
DynamicExpression.ParseLambda<A, bool>(
"AStuff.Contains(#0)",
"ac"
);
Expression<Func<A,bool>> predicateB =
DynamicExpression.ParseLambda<A, bool>(
"AStuff.Contains(#0)",
"bc"
);
dbModel.As.Where("#0(it) OR #1(it)", predicateA, predicateB);
it explodes with an exception:
NotSupportedException>>The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
It may be possible to build the entire query in the first form, but the later would be more useful in our scenario. Is there a way to make that work?
Just use Predicate Builder to join (Or/And) multiple predicates.

Extension method on Enum causes Linq Query to Fail

I have an extension method on an Enum called GetName which returns a string. I'm using it in linq to entity framework to select rows with a specific product name. However, when the code is getting executed it's throwing a NotSupportedException
LINQ to Entities does not recognize the method System.String
GetName(Tool.ViewModels.Product) method, and this method cannot be
translated into a store expression.
Here is the code I am executing:
try
{
//Linq to Entity Framework
var contextRow = Contexts.Data.Source.SingleOrDefault(p => p.Product == Product.ProductOne.GetName());
}
catch (Exception e)
{
throw e;
}
Does Linq not recognize extension methods in its evaluations? Or is there something more going on?
Under the hood the Entity Framework is mapping the code you type into SQL queries. When it sees the call to GetName it sees a call into a user defined function. It has no power to translate, what essentially amounts to raw IL, into a well formed SQL query. This is why you are getting that exception
when using LINQ, you have to define a context and within that context perform operations on the database, for example the code you want to run, you can be this way
using (ModelLinq _context = new ModelLinq()){
var rows= _context.anyTable.Where(p=> p.anyColumn == value);
}
And Entity Framework is a mapping of your data base for you to execute SQL queries with lenguale c #
regards!

LINQ to Nhibernate user defined function in where clause

I'm trying to do the following:
var query =
(from a in session.Query<A>()
where a.BasicSearch(searchString) == true
select a);
But it keeps giving me this exception "System.NotSupportedException"!
Any idea how to solve this?
It is not possible to use user-defined functions in a LINQ query. The NHibernate linq provider does not 'know' how to translate your function into SQL.
LINQ to NHibernate works by inspecting the LINQ expression that you provide at runtime, and translating what it finds in this expression tree into a regular SQL expression. Here's a good article to get some background on expression trees: http://blogs.msdn.com/b/charlie/archive/2008/01/31/expression-tree-basics.aspx
You CAN reuse predicates like this in another way however, using the techniques discussed here. (I'm not sure if this works with NHibernate however.) IF it works it would look something like this:
// this could be a static method on class A
public static Expression<Func<A, bool>> BasicSearch(string criteria)
{
// this is just an example, of course
// NHibernate Linq will translate this to something like
// 'WHERE a.MyProperty LIKE '%#criteria%'
return a => criteria.Contains(a.MyProperty);
}
Usage:
from a in Session.Query<A>().Where(A.BasicSearch(criteria))
UPDATE: apparently there will be issues with NHibernate. See this blog post for a version that ought to work.
It is possible to call your own and SQL functions, but you have to make a wrapper for them so that NHibernate knows how to translate the C# to SQL.
Here's an example where I write an extension method to get access to SQL Server's NEWID() function. You would use the same techniques to get access to any other function on your database server, built-in or user-defined.
Some examples to extend NHibernate LINQ:
http://fabiomaulo.blogspot.se/2010/07/nhibernate-linq-provider-extension.html
https://nhibernate.jira.com/browse/NH-3301
Declare a BasicSearch extension method. Supposing your udf is on dbo:
using NHibernate.Linq;
...
public static class CustomLinqExtensions
{
[LinqExtensionMethod("dbo.BasicSearch")]
public static bool BasicSearch(this string searchField, string pattern)
{
// No need to implement it in .Net, unless you wish to call it
// outside IQueryable context too.
throw new NotImplementedException("This call should be translated " +
"to SQL and run db side, but it has been run with .Net runtime");
}
}
Then use it on your entities:
session.Query<A>()
.Where(a => a.SomeStringProperty.BasicSearch("yourPattern") == true);
Beware, trying to use it without referencing an entity in its usage will cause it to get evaluated with .Net runtime instead of getting it translated to SQL.
Adapt this BasicSearch example to whatever input types it has to handle. Your question was calling it directly on the entity, which does not allow your readers to know on how many columns and with which types it need to run.

Categories