EF Core, FromSqlRaw with USE HINT - sql

I'm confused !
I have an ASP.Net Core 3 WebApi application, and this works fine:
var results = _context.Users.ToList();
However, if I try to add a "HINT" to the SQL...
var results = _context.Users.FromSqlRaw("SELECT * FROM t_user OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'))").ToList();
... then it throws this exception...
Incorrect syntax near the keyword 'OPTION'.
Database 'HINT' does not exist. Make sure that the name is entered correctly.
Why would this SQL run successfully in SQL Server Management Studio, but get misunderstood by the WebApi ?
Update # 1
Damn. When you tell EF Core to use Raw SQL, it actually puts that SQL into a sub-clause, and this is why I'm seeing the error.
So, this is the (perfectly valid) SQL which I'm trying to run:
SELECT * FROM t_user
OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'))
..but, looking at SQL Server Profiler, EF Core is actually trying to this SQL, and this isn't valid...
SELECT [u].[user_id], [u].[user_name]
FROM (
SELECT * FROM t_user
OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'))
) AS [u]
Damn...
So how do we use Hints from EF Core ?
Update # 2
Because I'm using EF Core, I followed the instructions in this Microsoft article to add a DbCommandInterceptor to my query.
In their example, it intercepts the SQL and appends " OPTION (ROBUST PLAN)" to the string. It shows that it's trying to run this SQL:
SELECT [u].[user_id], [u].[user_name]
FROM t_user
OPTION (ROBUST PLAN)
If I take the Microsoft example, and change it to use my HINT, then I still get the same error of it saying "Database 'HINT' does not exist."
private static void ManipulateCommand(DbCommand command)
{
if (command.CommandText.StartsWith("-- Use hint: robust plan", StringComparison.Ordinal))
{
command.CommandText += " OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'))";
}
}
Ahhhh! Why can't I use a Hint ?!

It seems that for some reason USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE') is treated as USE <db_name>
USE
Changes the database context to the specified database or database snapshot in SQL Server.
Then the erorr: "Database 'HINT' does not exist." has perfect sense.
Now as for ENABLE_PARALLEL_PLAN_PREFERENCE it is undocummented query hint:
https://github.com/MicrosoftDocs/sql-docs/issues/2442
Unfortunately, we have to decline on adding information about ENABLE_PARALLEL_PLAN_PREFERENCE. That is an undocumented query hint that is meant for troubleshooting purposes and is to be used at the direction of Microsoft Support. We normally do not document certain query hints/trace flags intentionally as it may lead to performance issues or other unintended consequences.
So please be cautious when setting it as default or using in production code.
You could try to use OPTION(QUERYTRACEON 8649) which will the behave same as mentioned ENABLE_PARALLEL_PLAN_REFERENCE, but it will require administrator priviliges:
private static void ManipulateCommand(DbCommand command)
{
if (command.CommandText.StartsWith("-- Use hint: robust plan", StringComparison.Ordinal))
{
command.CommandText += " OPTION(QUERYTRACEON 8649)";
}
}
To sum up: Before setting this hint in application code, I would recommend resolve the real underlying issue(maxdop/Cost Threshold for Parallelism/...)
EDIT:
Raw SQL Queries
Composing with LINQ requires your raw SQL query to be composable since EF Core will treat the supplied SQL as a subquery. SQL queries that can be composed on begin with the SELECT keyword. Further, SQL passed shouldn't contain any characters or options that aren't valid on a subquery, such as:
A trailing semicolon
On SQL Server, a trailing query-level hint (for example, OPTION (HASH JOIN))
On SQL Server, an ORDER BY clause that isn't used with OFFSET 0 OR TOP 100 PERCENT in the SELECT clause

When you tell Ef core the output of your query is an entity then EF wrap your query into that entity but if you define a Keyless Entity Types for your query, EF first execute the query and then map result into your model.
first create model like your user model:
public class KeyLessUser
{
public string UserId { get; set; }
public string UserName { get; set; }
}
And add it to DbContext with HasNoKey:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<KeyLessUser>().HasNoKey();
}
Finally execute your query like this:
var results = _context.Set<KeyLessUser>().FromSqlRaw("SELECT * FROM t_user OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'))").ToList();
Tested and works fine.

Related

OData Case In-Sensitive filtering in Web API?

By default ODataController performs case-sensitive search. How do we extend this behaviour to perform case-insensitive search.
public class NammaODataController : ODataController
{
[EnableQuery]
public IQueryable<FD> GetFD(ODataQueryOptions qo)
{
return _ctx.FDs.AsQueryAble();
//EF takes care to apply odata query operators internally
//after control passes from action
}
}
I referred this SO seems we can leverage Expression Tree, but at
which extensiblility point?
Is there any way I could intercept the query and customise EF / Web Api piplelie to achieve this?
Here's an example: /FD?$filter=tolower(Pr_Name) eq tolower('TAMARA') -
this should return all people with name 'Tamara' (case
insensitive, could be 'TAMARA', 'tamara', 'Tamara', etc. Hope this will
help, to put forward my point.
EDIT:
Case-senitivity in ODATA Web API queries has nothing to do with SQL Collation. This has been an issue with Microsoft OData framework.
With substringof operator
void Main()
{
var lower = _ctx.FD
.Where(sv => sv.Pr_Name.Contains("tamara"))
.Take(1)
.ToList();
Console.WriteLine("LOWER CASE AZURE SQL OUTPUT");
Console.WriteLine(lower);
Console.WriteLine("UPPER CASE AZURE SQL OUTPUT");
var upper = _ctx.FD
.Where(sv => sv.Pr_Name.Contains("TAMARA"))
.Take(1)
.ToList();
Console.WriteLine(upper);
}
With eq operator
void Main()
{
var lower = FD
.Where(sv => sv.Pr_Name == tamara A TOPOLESKI")
.Take(1)
.ToList();
Console.WriteLine("LOWER CASE AZURE SQL OUTPUT");
Console.WriteLine(lower);
Console.WriteLine("UPPER CASE AZURE SQL OUTPUT");
var upper = FD
.Where(sv => sv.Pr_Name == "TAMARA TOPOLESKI")
.Take(1)
.ToList();
Console.WriteLine(upper);
}
Edit
You are right, the problem is not related to your collation. It has to do with the odata expression that you are using. In your updated test query you are using Contains which gets translated into a LIKE with wild cards (any string of zero or more characters) at either end of the search value. However, in your odata expression you are using eq which will get translated into a = in EF expression and then again in the SQL query. As the Pr_Name values you have shown in your output are not exactly equal to "Tamara" but contain "Tamara" the odata queries will not bring back any data.
What you need is the odata filter expression substringof which would get translated by EF to Contains which is translated in sql to LIKE with wild cards on either side of the search value.
/FD?$filter=substringof(Pr_Name,'tamara')
For more filter expressions refer to Using Filter Expressions
As #ThomasKoelle stated in the comments this has to do with the collation of the column(s). The OData pipeline (you have defined above) uses Entity Framework which converts the passed in expression into a query for the database. So the OData expression has nothing to do with it and neither does the built EF query.
If you have a case sensitive collation then you would have to make all your search terms and search columns the same case which is very bad for performance because those search clauses would not be SARGable.
For most DBMS's the collation of the column is inherited from the default collation of the database which is inherited from the instances default collation. You can override at each level but it must be done explicitly at the time the instances (schema / database) are defined.

Entity Framework 6: Store update, insert, or delete statement affected an unexpected number of rows (0)

I am hoping someone can help me identify the cause of the following error when doing on update in Entity Framework.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
From what I have read, it means that data has changed between the fetch and save, however this is not the case in my situation. (Single developer running the application locally. I also have viewed the data in the database before I attempt to save, and it is the exact same as when I did the fetch. Also able to reproduce this on command.)
I am not sure if this is relevant, but it seems to be the only factor that is different than other entities that are working. I have an entity that represents a table with a composite key. The value that I am updating is one of the values that makes up the composite key. There is only one record in the table at the moment, so I know there is no primary key violation stuff going on.
Does anyone know what steps I can take to find out what the actual problem is?
Thanks
If you have instead of trigger on table you are inserting to, it causes this error. You have to rewrite trigger to after insert trigger, or at the end of trigger select new generated id. Maybe for update there is similar problem. Look at query entity framework generates - it can help you to see what's going on.
EDIT:
To see generated queries set logging:
public class CustomContext : DbContext
{
public CustomContext()
: base("name=CustomString")
{
// configure writing queries to console
Database.Log = Console.Write;
}
// other context stuf ...
}
Or use some profiler (for sql server express you can use http://expressprofiler.codeplex.com/).
The problem resides in the fact that you call the method _dataContext.SaveChanges();, but nothing changed in the data. To avoid this error try this:
public void EditCustomer(Customer customer)
{
_dataContext.Customer.Attach(customer);
var entry = _dataContext.Entry(customer);
if(entry.Property(e => e.DeviceId).CurrentValue != entry.Property(e => e.DeviceId).OriginalValue)
{
entry.Property(e => e.DeviceId).IsModified = true;
}
if(entry.Property(e => e.Name).CurrentValue != entry.Property(e => e.Name).OriginalValue)
{
entry.Property(e => e.Name).IsModified = true;
}
if(entry.Property(e => e.DeviceId).IsModified || entry.Property(e => e.Name).IsModified)
{
_dataContext.SaveChanges();
}
}
I hope this helps you.
#DonPablone
I've encountered with such error message, my environment is as follows, SQL server 2016 along with ef6 database first and the issue was that the database developer did not define an identity seed column in the Id column of the table I'm inserting data into, and of course the issue solved when I updated the table design, so I'm sharing this experience in case if anyone got the same issue.
If by any chance, we have the same problem when trying to update record using Attach() and then SaveChanges() combination? This may help...
I am using SQLite DB and its EF provider (the same code works in SQLServer DB without problem).
I found out, when your DB column has GUID (or UniqueIdentity) in SQLite and your model is nvarchar, SQLIte EF treats it as Binary(i.e., byte[]) by default. So when SQLite EF provider tries to convert GUID into the model (string in my case) it will fail as it will convert to byte[]. The fix is to tell the SQLite EF to treat GUID as TEXT (and therefore conversion is into strings, not byte[]) by defining "BinaryGUID=false;" in the connectionstring (or metadata, if you're using database first) like so:
<connectionStrings>
<add name="Entities" connectionString="metadata=res://savetyping...=System.Data.SQLite.EF6;provider connection string="data source=C:\...\db.sqlite3;Version=3;BinaryGUID=false;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
Link to the solution that worked for me:
How does the SQLite Entity Framework 6 provider handle Guids?

Is it possible to make `#SQLDelete` take the `hibernate.default_schema` parameter into account?

In a webapp, I use Hibernate's #SQLDelete annotation in order to "soft-delete" entities (i.e. set a status column to a value that denotes their "deleted" status instead of actually deleting them from the table).
The entity code looks like this :
#Entity
#SQLDelete(sql="update pizza set status = 2 where id = ?")
public class Pizza { ... }
Now, my problem is that the web application doesn't use the owner of the schema to which the tables belong to connect to the DB. E.g. the schema (in Oracle) is called pizza, and the db user the webapp uses to connect is pizza_webapp. This is for security reasons. The pizza_webapp user only has select/update/delete rights, it can't modify the structure of the DB itself. I don't have any choice here, it is a policy that I can't change.
I specify the name of the schema where the tables actually are with the hibernate-default_schema parameter in hibernate config :
<property name="hibernate.default_schema">pizza</property>
This works fine for everything that goes through mapped entities, Hibernate knows how to add the schema name in front of the table name in the SQL it generates. But not for raw SQL, and the #SQLDelete contains raw SQL. This is executed 'as is' and results in a "table or view not found error".
So far we worked around the issue by adding synonyms to the pizza_webapp schema, pointing to the pizza schema. It works, but it is not fun to maintain across multiple DBs when entities are added.
So, is it possible to make #SQLDelete take the hibernate.default_schema parameter into account ?
(NB: Obviously I don't want to hard-code the schema name in the SQL either...)
Yes, it is possible:
#SQLDelete(sql="update {h-schema}pizza set status = 2 where id = ?")
I could not find any Hibernate solution to this problem. However I found a work-around based on an Oracle feature. I do this in to my session before using it :
//set the default schema at DB session level for raw SQL queries (see #SQLDelete)
HibernateUtil.currentSession().doWork(new Work() {
#Override
public void execute(Connection connection) throws SQLException {
connection.createStatement().execute("ALTER SESSION SET CURRENT_SCHEMA="+HibernateUtil.getDefaultSchema());
}
});
I works fine, but unfortunately only on Oracle (which is fine for us for now at least). Maybe there are different ways to achieve the same thing on other RDBMS as well ?
Edit: the the getDefaultSchema() method in my HibernateUtil class does this to get the default schema from Hibernate's config :
defaultSchema = config.getProperty("hibernate.default_schema");
where config is my org.hibernate.cfg.Configuration object.

Entity Framework 4.1 Raw SQL

I am developing an ASP.Net MVC 3 application using Entity Framework 4.1. For a particular complex query that I need to execute I have decided to write a raw SQL query and pass it to the built in dbSet.SqlQuery method.
I have a Service method like below where I assign the SQL query to a string variable called query. As the query is passed two parameters, I have parameterized these to prevent SQL Injection.
public IList<User> GetAvailableLocums(int shiftID, int shiftDateID)
{
var query ="Select .... where t1 = #p0 and t2 = #p1";
ObjectParameter _shiftID = new ObjectParameter("p0", shiftID);
ObjectParameter _shiftDateID = new ObjectParameter("p1", shiftDateID);
object[] parameters = new object[] { _shiftID, _shiftDateID };
return _UoW.User.GetWithRawSql(query, parameters).ToList();
}
I then pass the query and the parameters to a method in my repository which executes the query for me.
public IEnumerable<TEntity> GetWithRawSql(string query, params object[] parameters)
{
return dbSet.SqlQuery(query, parameters).ToList();
}
I know the query is correct as I have tested it in SQL Server Management Studio, however, I currently get the following error when I try to run this code
No mapping exists from object type System.Data.Objects.ObjectParameter
to a known managed provider native type
Does anyone have any suggestions as to how I can fix this?
Thanks for your help.
Folks
The problem was that I was using ObjectParameter to create my Parameters. I instead changed this to SqlParameter and it worked fine. See below.
Change from this
ObjectParameter _shiftID = new ObjectParameter("p0", shiftID);
To this
SqlParameter _shiftID = new SqlParameter("p0", shiftID);
And it worked. Hope this helps someone else.
From a quick Google search, it looks like your close. I think you are missing setting the return type for your SQL query:
return dbSet.SqlQuery<TEntity>(query, parameters).ToList();
This just tells Entity Framework how to map it.

Unit testing NHibernate application with SQLite: it writes to the database but cannot read back

I have an application using NHibernate that is already deployed and working properly, and I'm re-factoring the unit tests to use SQLite for improved performance, and to keep unit test data out of the "real" database.
I have a simple test that creates an Calendar entity, saves it, then tries to read it back and verifies that it's the same object. The write works, but the subsequent select to read it back returns 0 records. A Calendar has a GUID as a primary key, and I understand that requires an extra parameter on the SQLite connection string. This is my connection string:
data source=:memory:;Version=3;New=true;Pooling=true;Max Pool Size=1;BinaryGuid=False
Through the logged SQL statements coming from NHibernate, I see the inserts to write the entity and its dependencies, then the subsequent select statement. It all looks good, but nothing is selected. If I use a file database instead of an in-memory database, I can open up the table in Visual Studio's Server Explorer, and I see the correct data in the tables. If I write a query to try selecting the record, like so:
SELECT CalendarID, Name, Description
FROM dbo_Calendars
WHERE (CalendarID = 'a9cd9820-1694-4645-88d4-f682c5a6b9cc')
it also fails to select anything. I think it's an issue with GUID handling, but I'm flummoxed.
Update
Here's what the test case looks like:
[Test]
public void SaveAndLoadCalendar()
{
Guid calId;
DAOFactory factory = (DAOFactory)DAOFactory;
ISession s = factory.SessionManager.CurrentSession;
using (var tx = s.BeginTransaction())
{
Calendar cal = new Calendar("Test Calendar", CalendarType.Test);
cal.Active = true;
cal.Browsable = true;
s.Save(cal);
tx.Commit();
calId = cal.ID;
}
Logger.InfoFormat("Calendar ID is {0} ", calId);
s.Clear();
using (var tx2 = s.BeginTransaction())
{
Calendar cal = s.Get<Calendar>(calId);
Assert.IsNotNull(cal, "Could not retrieve saved calendar");
Assert.AreEqual("Test Calendar", cal.Name, "Saved calendar not equal to original calendar");
}
}
I would guess that the transaction handling could be the problem.
So maybe the transaction inserting the record is not yet committed and so the (different) transaction performing the select does not yet see the new data - so the select returns nothing.
I figured it out, and the problem isn't NHibernate or SQLite, it's me. Each Calendar has an associated Theme. In our production database, these are manually entered, and expected to exist in advance. Now that I'm using SQLite for testing, I'm starting with an empty database, and this reference data isn't pre-populated. NHibernate's Select statement to fetch the Calendar uses an inner join on the Themes table, and with nothing in that table, the select will return empty. D'oh.
After updating my test setup code to save the default theme, the test passes.