Interception not working as expected with Entity Framework 6 - sql-server

I'm trying to follow the interception example shown here to get it working with EF 6 but running into a problem with the function RewriteFullTextQuery as shown in Figure 1. The interception seems to work but it does not actually execute the logic in the for loop of the RewriteFullTextQuery method because the cmd.Parameters.Count is always zero. Furthermore the cmd.CommandText property seems to be displaying the correct SQL query which I take as another piece of evidence that the interception is working correctly.
Figure 1: RewriteFullTextQuery Code Excerpt
public static void RewriteFullTextQuery(DbCommand cmd)
{
string text = cmd.CommandText;
for (int i = 0; i < cmd.Parameters.Count; i++)
{
DbParameter parameter = cmd.Parameters[i];
if (parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength))
{
The RewriteFullTextQuery function is being called by the ReaderExecuting function shown in Figure 2 which gives it the command argument that is causing all the trouble.
Figure 2: ReaderExecuting Function
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
RewriteFullTextQuery(command);
}
Even though my code isn't exactly the same as the example, the interception seems to be working so it is making me wonder what conditions is it that will populate the command to have a Parameters.Count of more than zero?

It works only if you pass the parameter to the query as a variable. If you use a literal EF won't use parameters.
I mean, this won't generate any parameter
context.Notes.Where(_ => _.NoteText == "CompareValue").Count();
This will
string compareValue = "CompareValue";
context.Notes.Where(_ => _.NoteText == compareValue).Count();

It turns out that it is because of the way Entity Framework generates the SQL. If you pass in a string literal as your search value to your LINQ statement it does not generate a SQL that makes use of a parameter. But if you pass in your search value as a variable, it will generate the SQL that utilizes a parameter. A solution (for dynamic queries) and more details can be found on this blog.

Related

EF Core 3.1.9 - FromRawSql using stored procedures stopped working - 'The underlying reader doesn't have as many fields as expected.'

At one point using FromSqlRaw to call stored procedures worked for me. I did not change anything in the project but now calling any stored procedure using FromSqlRaw returns
The underlying reader doesn't have as many fields as expected
I removed the model from the project and performed a BUILD. Then added the model back with no luck. I reduced the model and stored procedure to return a single column, no luck.
I tried adding Microsoft.EntityFrameworkCore.Relational as a dependency, no luck. All my unit test that use FromSqlRaw to call a stored procedure return the same error and at one time they all worked.
I have received Windows updates but nothing I know about that would have affected EF Core. I have run through all internet problem solving I can find. I am starting to think I will need to use ADO as a work around but I do not want a work around when it worked for me at one point. Something changed on my machine but I am not sure what to cause this problem.
Here is my test method in case my code is messed up. It is very straight forward not much to mess up. I tried the "var" out of desperation.
[TestMethod]
public void WorkOrderBOMGridABS()
{
List<WorkOrderBOMGridABS> baseList = new List<WorkOrderBOMGridABS>();
using (WorkOrderDataContext context = new WorkOrderDataContext())
{
var param = new SqlParameter[] {
new SqlParameter() {
ParameterName = "#WorkOrderId",
SqlDbType = System.Data.SqlDbType.Int,
Direction = System.Data.ParameterDirection.Input,
Value = 38385
}
};
baseList = context.WorkOrderBOMGridABS.FromSqlRaw("[dbo].[WorkOrderBOMGridABS] #WorkOrderId", param).ToList();
//var results = context.WorkOrderBOMGridABS.FromSqlRaw("[dbo].[WorkOrderBOMGridABS] #WorkOrderId", param).ToList();
Assert.IsNotNull(baseList);
}
}
I was using an old table to get the Unit Of Measure value that had an integer ID value. I switched it to use a new table with a VARCHAR ID value. Making this change to the stored proc and model code allowed the FromRawSql to work. Not sure why because while the integer ID value was getting an integer, either 0 or number other than 0, it was a valid value for the model. Any error message I received did not mention this UnitId field. It was a pain but I am glad it is resolved. At least until the next error I run into that much is guaranteed.

How to use a SQL output parameter with FromSqlInterpolated? Or alternative

Using ASP.Net core 3.0. my Database query works - I can get the result set (records), but how to pass/get the output parameter? Also would like to know if there's a way to get the return value.
I tried using different ways to call the SQL query and the only one I was able to get working was FromSqlInterpolated, but open to different methods.
This code works for me, but I want to pass an additional parameter that can get populated as an output parameter (which is working when I test it by calling the stored proc from within SQL Server).
var result = _context.Users
.FromSqlInterpolated($"EXEC mystoredproc {user.Username} ").AsEnumerable().FirstOrDefault();
I tried creating a variable before the call
string out1 = null;
And then including that in the call but I can't figure out the syntax and I'm not sure if it's supported with this method.
var result = _context.Users
.FromSqlInterpolated($"EXEC mystoredproc {user.Username}, OUTPUT {out1}").AsEnumerable().FirstOrDefault();
Console.WriteLine(out1);
Hoping someone can point me in the right direction - would like to know both how to use the output parameter and how to get the return value, not just the record set. Thank you.
The answer accepted does not seem to work for me.
However I finally manage to have the OUTPUT parameter work by using the following code
var p0 = new SqlParameter("#userName", {user.Username});
var p1 = new SqlParameter("#out1", System.Data.SqlDbType.NVarChar, 20) { Direction = System.Data.ParameterDirection.Output };
string out1 = null;
var result = _context.Users
.FromSqlRaw($"EXEC mystoredproc #userName, #out1 OUTPUT ", p0, p1).AsEnumerable().FirstOrDefault();
Console.WriteLine(p1.Value); // Contains the new value of the p1 parameter
Just a litte warning, the p1 value property will be changed only when the request will be executed (by an AsEnumerable, Load() or anything that forces the Sql execution)

Simplejdbccall Stored Procedure withNamedBinding true

I want to execute stored procedure with dynamic parameters with SimpleJdbcCall. In total I have 6 optional parameters in SQL server SP, out of them I must be able to pass any or none. My SP executes fine as expected in MS Studio. But not by SimpleJdbcCall. I tried in many ways and one of them I tried is withNamedBinding. But it gives Input Syntax error near "=" as below.
this.simpleJdbcCall = new SimpleJdbcCall(jdbcTemplateObject)
.withNamedBinding()
.withSchemaName("dbo")
.withProcedureName("EmployeeDetails")
.useInParameterNames(
paramNameArray)
.returningResultSet("detailReportData", BeanPropertyRowMapper.newInstance(Employee.class));
Map<String,Object> out = this.simpleJdbcCall.execute(sqlSource);
Log:
2019-01-31 18:14:49 DEBUG SimpleJdbcCall:405 - The following
parameters are used for call {call dbo.EmployeeDetails(empCode => ?,
empName => ?, empLoc => ?)} with {empCode=0, empName='hgkghdkgf',
empLoc='kjhjk'} 2019-01-31 18:14:49 DEBUG DispatcherServlet:993 -
Could not complete request
org.springframework.jdbc.UncategorizedSQLException:
CallableStatementCallback; uncategorized SQLException for SQL [{call
dbo.EmployeeDetails(empCode => ?, empName => ?, empLoc => ?)}]; SQL
state [S0001]; error code [102]; Incorrect syntax near '='.; nested
exception is com.microsoft.sqlserver.jdbc.SQLServerException:
Incorrect syntax near '='.
I had faced similar type of problem and found few solutions in this stack overflow question. But in my case I was using MS-SQL server and it was giving me this same syntax error. While searching for solutions I came across this example of Spring (See Section 11.5.6. Declaring parameters to use for a SimpleJdbcCall).
Just in case if the link becomes unavailable, here is what it says
We can opt to declare one, some or all of the parameters explicitly. The parameter metadata is still being used. By calling the method withoutProcedureColumnMetaDataAccess we can specify that we would like to bypass any processing of the metadata lookups for potential parameters and only use the declared ones. Another situation that can arise is that one or more in parameters have default values and we would like to leave them out of the call. To do that we will just call the useInParameterNames to specify the list of in parameter names to include.
And here is the sample code
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor =
new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}
// ... additional methods
}
I am not sure why but the withNamedBinding() method seems to not work properly with ms-sql server and creates the syntax error.
So in the above solution the work around is to
not to use the .withNamedBinding() method.
use the .withoutProcedureColumnMetaDataAccess() method.
while passing your parameter array to the .useInParameterNames() method, make sure you pass it in the sequence it is declared in the proc (as we are not using name binding). My out parameter was declared few parameters before the last one and so it was giving me error while converting var to int as my out param was int.
So now your solution should look something like this :
this.simpleJdbcCall = new SimpleJdbcCall(jdbcTemplateObject)
.withSchemaName("dbo")
.withProcedureName("EmployeeDetails")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames(paramNameArray)
.returningResultSet("detailReportData", BeanPropertyRowMapper.newInstance(Employee.class));
Map<String,Object> out = this.simpleJdbcCall.execute(sqlSource);
Give it a try and see if this works for you.

Using CONTAINS from HQL / Criteria API

I'm using NHibernate 2.1.2.4000GA. I'm trying to use SQL Server's CONTAINS function from within HQL and the criteria APIs. This works fine in HQL:
CONTAINS(:value)
However, I need to qualify the table in question. This works fine:
CONTAINS(table.Column, :value)
However, I need to search across all indexed columns in my table. I tried this:
CONTAINS(table.*, :value)
But I get:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException : Exception of type 'Antlr.Runtime.MissingTokenException' was thrown. near line ... [select table.Id from Entities.Table table where CONTAINS(table.*,:p0) order by table.Id asc]
at NHibernate.Hql.Ast.ANTLR.ErrorCounter.ThrowQueryException()
at NHibernate.Hql.Ast.ANTLR.HqlParseEngine.Parse()
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.Parse(Boolean isFilter)
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.DoCompile(IDictionary`2 replacements, Boolean shallow, String collectionRole)
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.Compile(IDictionary`2 replacements, Boolean shallow)
at NHibernate.Engine.Query.HQLQueryPlan..ctor(String hql, String collectionRole, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)
at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(String queryString, Boolean shallow, IDictionary`2 enabledFilters)
at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(String query, Boolean shallow)
at NHibernate.Impl.AbstractSessionImpl.CreateQuery(String queryString)
So it would seem the HQL parser chokes on the asterisk. I thought of doing this:
CONTAINS(table.Column1, :value) or CONTAINS(table.Column2, :value)
Not only is this inefficient, it can also yields incorrect results depending on the full text predicate in :value.
I tried customizing my dialect as per these instructions, but that doesn't help because you're still left with the same problem: specifying table.* causes the HQL parser to fall over.
I thought of specifying query substitutions:
<property name="query.substitutions">TABLECONTAINS(=CONTAINS(table.*,</property>
And then simply doing:
TABLECONTAINS(:value)
But that does not work. I'm not sure why - judging by the resultant error, the substitution just doesn't take place because "TABLECONTAINS" is still present in the query. Besides, this wouldn't work for all cases because I'd need to know the alias of the table and dynamically substitute it in.
Therefore, I rolled an interception-based approach:
using System;
using NHibernate;
using NHibernate.SqlCommand;
public class ContainsInterceptor : EmptyInterceptor
{
public override SqlString OnPrepareStatement(SqlString sql)
{
var indexOfTableContains = sql.IndexOfCaseInsensitive("TABLECONTAINS(");
if (indexOfTableContains != -1)
{
var sqlPart = sql.ToString();
var aliasIndex = sqlPart.LastIndexOf("Table ", indexOfTableContains, StringComparison.Ordinal);
if (aliasIndex == -1)
{
return sql;
}
aliasIndex += "Table ".Length;
var alias = sqlPart.Substring(aliasIndex, sqlPart.IndexOf(" ", aliasIndex, StringComparison.Ordinal) - aliasIndex);
sql = sql.Replace("TABLECONTAINS(", "CONTAINS(" + alias + ".*,");
}
return base.OnPrepareStatement(sql);
}
}
This works and I will now be able to sleep tonight, but I do feel a sudden desire to attend London's impending papal procession and shout out a confession.
Can anyone suggest a better way to achieve this?
Why not use an ISQLQuery instead?
You can still retrieve entities, see http://nhibernate.info/doc/nh/en/index.html#querysql-creating
I would think that a custom dialect would be the appropriate way to handle this. You can find some guidance in this article. I've used this approach to register SQL Server-specific functions like ISNULL for use in our projects.

Google Datastore problem with query on *User* type

On this question I solved the problem of querying Google Datastore to retrieve stuff by user (com.google.appengine.api.users.User) like this:
User user = userService.getCurrentUser();
String select_query = "select from " + Greeting.class.getName();
Query query = pm.newQuery(select_query);
query.setFilter("author == paramAuthor");
query.declareParameters("java.lang.String paramAuthor");
greetings = (List<Greeting>) query.execute(user);
The above works fine - but after a bit of messing around I realized this syntax in not very practical as the need to build more complicated queries arises - so I decided to manually build my filters and now I got for example something like the following (where the filter is usually passed in as a string variable but now is built inline for simplicity):
User user = userService.getCurrentUser();
String select_query = "select from " + Greeting.class.getName();
Query query = pm.newQuery(select_query);
query.setFilter("author == '"+ user.getEmail() +"'");
greetings = (List<Greeting>) query.execute();
Obviously this won't work even if this syntax with field = 'value' is supported by JDOQL and it works fine on other fields (String types and Enums). The other strange thing is that looking at the Data viewer in the app-engine dashboard the 'author' field is stored as type User but the value is 'user#gmail.com', and then again when I set it up as parameter (the case above that works fine) I am declaring the parameter as a String then passing down an instance of User (user) which gets serialized with a simple toString() (I guess).
Anyone any idea?
Using string substitution in query languages is always a bad idea. It's far too easy for a user to break out and mess with your environment, and it introduces a whole collection of encoding issues, etc.
What was wrong with your earlier parameter substitution approach? As far as I'm aware, it supports everything, and it sidesteps any parsing issues. As far as the problem with knowing how many arguments to pass goes, you can use Query.executeWithMap or Query.executeWithArray to execute a query with an unknown number of arguments.

Resources