Are scalar udfs accessible to execute with EF?
Thanks
Yes. This is supported, here is a quick walk through on howto do it.
Alter your EF SSDL:
<Function Name="AvgStudentGrade" ReturnType="decimal" Schema="dbo" >
<Parameter Name="studentId" Mode="In" Type="int" />
</Function>
Add a method stub with the appropriate attributes:
[EdmFunction("SchoolModel.Store", "AvgStudentGrade")]
public static decimal? AvgStudentGrade(int studentId)
{
throw new NotSupportedException("Direct calls are not supported.");
}
Use it:
var students = from s in context.People
where s.EnrollmentDate != null
select new
{
name = s.LastName,
avgGrade = AvgStudentGrade(s.PersonID)
};
More info and full sample at:
http://msdn.microsoft.com/en-us/library/dd456847(VS.100).aspx
Suppose you have a table with the name MyDbSet, it can be any table you want. Then create a record with guid "test", then use the approach below to execute a ef scalar function and return the result.
MyDbSet
.Where(o => o.Guid == "test")
.Select(o => EF.Functions.ToTsVector(request.Keywords))
.FirstOrDefault();
Related
Libs employed:
Devart.Data.dll => 5.0.1878.0
Devart.Data.MySql.dll => 8.10.1086.0
Devart.Data.MySql.Design.dll => 8.10.1086.0
Devart.Data.MySql.Entity.EF6.dll => 8.10.1086.0
EntityFramework => 6.2.0
We are targeting .net4.7.1 from an ASP.NET MVC5 project. Our repositories are build on top of an EF .edmx (db-first approach). Our queries look like so:
var results = _db.NB_FILTERS
.Select(x => new ReportFiltersDTO { IsHiddenSubFilter = x.NFS_SUBFILTER_YN ?? false })
.ToList();
NFS_SUBFILTER_YN is declared as a boolean in our .edmx aka:
<Property Name="NFS_SUBFILTER_YN" Type="boolean" />
The NB_FILTERS ddl is like so:
CREATE TABLE NB_FILTERS (
[...]
NFS_SUBFILTER_YN BIT(1) NULL,
[...]
)
In such a scenario if certain rows are null the given linq expression returns 'true' for all of them instead of 'false'. The culprit appears to be in the auto-generated sql:
actual => CASE WHEN Extent1.NFS_SUBFILTER_YN IS NULL THEN 0 ELSE Extent1.NFS_SUBFILTER_YN END
corrected => CASE WHEN Extent1.NFS_SUBFILTER_YN IS NULL THEN 1 ELSE CAST(Extent1.NFS_SUBFILTER_YN AS SIGNED) END
How can one workaround this bug without changing en-masse the underlying tables and/or the linq statements themselves until an actual fix is rolled out by Devart?
Sidenotes:
For those interested we've notified the devart devs about this issue in hopes that it will be addressed at some point:
https://forums.devart.com/viewtopic.php?f=2&t=36955
Interestingly enough such queries work when the columns involved are nullable decimal(x,y) columns [the auto-generated sql correctly employs CAST(... AS SIGNED)]. It appears that somehow bit(x) was omitted from the list of types which are eligible for such handling.
As a temporary stop-gap solution we opted to use an EF6 interceptor on mysql in order to detect and correct on-the-fly sql statements targeting boolean columns (thankfully most if not all all of our boolean columns have the _YN postfix which helps a great deal):
using System.Data;
using System.Data.Common;
using System.Data.Entity.Infrastructure.Interception;
using System.Text.RegularExpressions;
namespace Some.Namespace
{
//to future maintainers the devart mysql drivers for ef6 ver8.10.1086.0 suffer from what appears to be a very annoying b.ug which cause nullable bit(1) columns set to null
//to future maintainers to always be materialized to true when we evaluate them like so
//to future maintainers
//to future maintainers x.SOME_BOOLEAN_COLUMN ?? false
//to future maintainers
//to future maintainers this is obviously flat out wrong and to remedy this issue we intercept the offending sql snippets and hotfix them like so
//to future maintainers
//to future maintainers before => CASE WHEN Extent1.NFS_SUBFILTER_YN IS NULL THEN 1 ELSE Extent1.NFS_SUBFILTER_YN END
//to future maintainers after => CASE WHEN Extent1.NFS_SUBFILTER_YN IS NULL THEN 1 ELSE CAST(Extent1.NFS_SUBFILTER_YN AS SIGNED) END
//to future maintainers
public sealed class MySqlHotFixerCommandInterceptor : IDbCommandInterceptor
{
//beforeexecution
public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) => LogWarningBeforeExecutionIfSynchronousExecutionIsFound(command, interceptionContext);
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) => LogWarningBeforeExecutionIfSynchronousExecutionIsFound(command, interceptionContext);
public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) => LogWarningBeforeExecutionIfSynchronousExecutionIsFound(command, interceptionContext);
//afterexecution
public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) {}
public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) {}
public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) {}
static private void LogWarningBeforeExecutionIfSynchronousExecutionIsFound<TResult>(IDbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
if (interceptionContext.Exception != null)
return;
command.CommandText = FaultySwitchCaseSpotter1.Replace(command.CommandText, "CAST($3 AS SIGNED)");
command.CommandText = FaultySwitchCaseSpotter2.Replace(command.CommandText, "CAST($3 AS SIGNED)");
}
private static readonly Regex FaultySwitchCaseSpotter1 = new Regex(#"(?<=CASE\s+WHEN\s+(`?[a-zA-Z0-9_]+?`?[.])?`?[a-zA-Z0-9_]+?`?\s+IS\s+NULL\s+THEN\s+(0|1)\s+ELSE\s+)((`?[a-zA-Z0-9_]+?`?[.])?`?[a-zA-Z0-9_]+?`?)(?=\s*END)", RegexOptions.IgnoreCase);
private static readonly Regex FaultySwitchCaseSpotter2 = new Regex(#"(?<=CASE\s+WHEN\s+(`?[a-zA-Z0-9_]+?`?[.])?`?[a-zA-Z0-9_]+?_YN`?\s+IS\s+NULL\s+THEN\s+(\d+|.[a-zA-Z0-9_]+?|`?[a-zA-Z0-9_]+?`?[.]`?[a-zA-Z0-9_]+?`?)\s+ELSE\s+)((`?[a-zA-Z0-9_]+?`?[.])?`?[a-zA-Z0-9_]+?_YN`?)(?=\s*END)", RegexOptions.IgnoreCase);
}
}
The web.config/app.config needs to be tweaked as well like so:
<configuration>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
[...]
</defaultConnectionFactory>
<providers>
[...]
</providers>
<interceptors>
<!-- keep first -->
<interceptor type="Some.Namespace.MySqlHotFixerCommandInterceptor, Organotiki.Infrastructure.Core">
</interceptor>
[...]
</interceptors>
</entityFramework>
</configuration>
I've got an Entity Framework 4 entity model in my program. There's a stored function I've defined in my SQL Anywhere 12.0.1 database called GuidToPrefix:
CREATE OR REPLACE FUNCTION GuidToPrefix( ID UNIQUEIDENTIFIER ) RETURNS INT AS
BEGIN
RETURN CAST( CAST( ID AS BINARY(4) ) AS INT )
END;
Following the directions in this MSDN article, I added the function to my EDMX:
<Function Name="GuidToPrefix" ReturnType="int" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="DBA">
<Parameter Name="ID" Type="uniqueidentifier" Mode="In" />
</Function>
To be totally honest, I updated the model from the database and checked off the function in the list on the first tab of the wizard. I don't know if that makes a difference or not, but I can't see why it would.
According to the article, I need to add a definition of the function in a C# class. My problem is it doesn't tell me what class to put that in. Do I add an entirely new class? Do I create a new .CS file and do something like this:
public static DbFunctions {
[EdmFunction( "CarSystemModel.Store", "GuidToPrefix" )]
public static int GuidToPrefix( Guid id ) {
throw new NotSupportedException( "Direct calls to GuidToPrefix are not supported." );
}
}
or do I put that in a partial of the entities class?
partial MyEntities {
[EdmFunction( "CarSystemModel.Store", "GuidToPrefix" )]
public static int GuidToPrefix( Guid id ) {
throw new NotSupportedException( "Direct calls to GuidToPrefix are not supported." );
}
}
I have two projects where this entity model is used. One is a class library and the model is definied in it. The other is another class library in another solution that just uses it. I've tried both examples above and the query in the second class library generates this error from the compiler in both cases:
The name 'GuidToPrefix' does not exist in the current context
Obviously I'm not doing something right. Has anyone tried this and got it to work?
I found the answer to this one.
Recall that I created a file with a partial of the MyEntities class in my project where the Entity Model is defined:
partial MyEntities {
[EdmFunction( "CarSystemModel.Store", "GuidToPrefix" )]
public static int GuidToPrefix( Guid id ) {
throw new NotSupportedException( "Direct calls to GuidToPrefix are not supported." );
}
}
This was fine and it all compiled. My problem wasn't here but in the project where I have to use the function.
In that project, in a class called DataInterface, I have a method with code like this:
var query = from read in context.Reads
from entry in context.Entries
.Where( e => GuidToPrefix( read.ID ) == e.PrefixID && read.ID == e.ID )
.....
The problem was that I needed to add the name of the class that contained the C# declaration of the GuidToPrefix function in the Where clause. That is, I needed to write the above expression as:
var query = from read in context.Reads
from entry in context.Entries
.Where( e => MyEntities.GuidToPrefix( read.ID ) == e.PrefixID && read.ID == e.ID )
.....
This compiles and when it runs it uses the function in the database in the LEFT OUTER JOIN as I wanted.
I have scalar function:
CREATE FUNCTION [dbo].[CheckLocation]
(
#locationId Int
)
RETURNS bit
AS
BEGIN
//code
END
I want to use it in Entity Framework context.
I have added this in the *.edmx file:
<Function Name="CheckLocation" ReturnType="bit" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" >
<Parameter Name="locationId" Type="int" Mode="In" />
</Function>
I have also created a partial class with method decorated with EdmFunctionAttribute:
public partial class MainModelContainer
{
[EdmFunction("MainModel.Store", "CheckLocation")]
public bool CheckLocation(int locationId)
{
throw new NotSupportedException("Direct calls not supported");
}
}
I try to use this function like this:
Context.CheckLocation(locationId);
And get NotSupportedException("Direct calls not supported").
It works within Select method, but it does not suit me.
Help please!
How can I call this function without select method?
you need to access it as a select
var students = context.Locations
.Select ( new { location= CheckLocation(locationId)}):
I am trying to do the following:
http://msdn.microsoft.com/en-us/library/dd456857.aspx
I created the function in the edmx file here just before the schema element:
<Function Name="YearsSince" ReturnType="Edm.Int32">
<Parameter Name="date" Type="Edm.DateTime" />
<DefiningExpression>
Year(CurrentDateTime()) - Year(AppliedDate)
</DefiningExpression>
</Function>
</Schema>
Now, I want to be able to use that in a query.
I created the following code in the ApplicantPosition partial class
[EdmFunction("HRModel", "YearsSince")]
public static int YearsSince(DateTime date)
{
throw new NotSupportedException("Direct calls are not supported.");
}
And I am trying to do the following query
public class Class1
{
public void question()
{
using (HREntities context = new HREntities())
{
// Retrieve instructors hired more than 10 years ago.
var applicantPositions = from p in context.ApplicantPositions
where YearsSince((DateTime)p.AppliedDate) > 10
select p;
foreach (var applicantPosition in applicantPositions)
{
Console.WriteLine(applicantPosition.);
}
}
}
}
The YearsSince is not recognized, the MSDN tutorial does not show exactly where I need to put the functio, so that might be my problem.
Your static YearsSince function must be defined in some class so if it is not Class1 you must use full identification with class name to call it. Check also this answer.
I am using SQL Reporting services to hit a WCF web service.
My query is:
<Query>
<Method Name="GetADTHistory" Namespace="http://tempuri.org/">
<Parameters>
<Parameter Name="personId"><DefaultValue>7885323F-DE8D-47E5-907D-2991C838FF3E</DefaultValue></Parameter>
</Parameters>
</Method>
<SoapAction>
http://tempuri.org/IResidentServiceFrontEnd/GetADTHistory
</SoapAction>
</Query>
My implementation is
public List<ResidentDataTypes.Person> GetADTHistory(Guid personId)
{
using (ResidentDataTypes.MyEntities entity = new ResidentDataTypes.MyEntities ())
{
var person = (from a in entity.People.Include("ResidentAdts")
where a.PersonId == personId
select a);
if (person.Count() > 0)
{
return person.ToList();
}
else
{
return new List<Person>();
}
}
}
This works fine if there are 2 or more ADT records. Reporting services correctly sees all the fields in the database. However if there is only 1 ADT record reporting services sees the 'Person' columns but none of the ADT records. Any ideas?
It looks like that you have an eager/lazy load issue. I suggest you to execute and test the query with linqpad to check if it really includes the ResidentAdts in the case of single record.
I also recommend you to check this thread: Linq to Entities Include Method Not Loading
Adding the "Linq" tag to your question would also helpful.