Disable CAST AS to optimize query in Entity Framework - c#

I'm using Entity Framework 5 and I want to select data from Oracle 10g database.
Problem is that the database table is huge and the query generated by Entity Framework is ineffective. I want to get rid of those CAST( [column] AS [type] ). Is there any setting to turn them off?
C# code:
var context = new APPDB();
var q = context.APP_TABLE.Where(i => i.ID == 123);
// This is how I did get the generated SQL query
var str = ((System.Data.Objects.ObjectQuery) q ).ToTraceString();
The generated query:
SELECT
CAST( "Extent1"."ID" AS number(10,0)) AS "C1",
"Extent1"."DESCRIPTION" AS "DESCRIPTION"
FROM "APP"."APP_TABLE" "Extent1"
WHERE (123 = ( CAST( "Extent1"."ID" AS number(10,0))))
What I want is the code to generate better performing query:
SELECT
"Extent1"."ID" AS "C1",
"Extent1"."DESCRIPTION" AS "DESCRIPTION"
FROM "APP"."APP_TABLE" "Extent1"
WHERE
"Extent1"."ID" = 123

Better later than never )
If you are using code first and manual mapping classes, use HasColumnType("INT") configuration for int properties.
For example:
var entity = builder.Entity<APP_TABLE>();
entity
.HasKey(x => x.ID)
.ToTable("APP_TABLE", "SCHEMA");
entity
.Property(x => x.ID)
.HasColumnType("INT");

I had a similar problem when using (short?) on the database and in an object property to perform the IQyueryable.
When using Equal() it usually do some bugs in code. When using the simple comparision, the T-SQL send an CAST() in two sides of the query. The bad thing: when using CAST in Transact SQL, Oracle does not use the INDEXES, making a simple select that runs in miliseconds direct in the database taking an eternity when running this way
Using this approach, the TSQL is transformed without the cast as an OR, that is less painfull than using the CAST. In case of using non nullable fields, the or does not appear, making the performance very high
In some cases, using Equal() gets rid of the cast conversion, but it not seems to work with short?
Lambda:
if (filter.Property.HasValue)
query = query.Where(w => new short?[] {
filter.Property}.Contains(w.Property));
SQL sent to Oracle:
WHERE (("Extent1"."Property" = :p__linq__0) OR (("Extent1"."Property" IS NULL) AND (:p__linq__0 IS NULL)))

Related

How to write EF query for Common Table Expression

This Sql query returns the expected data. I need to do the same in EF Query. I am not sure how to do it all in one EF query.
WITH cteproductactions (productkey, actionid) AS (
SELECT productkey, count(*) FROM productactions
GROUP BY productkey
HAVING count(*)>0
)
SELECT p.name,p.productkey,p.imageurl
FROM product p
INNER JOIN cteproductactions c on p.ProductKey=c.productkey
WHERE p.profileid=100
EF Query
var products = productRepo.Where(x => x.profileid=100);
var productkeys = products.Select(x => x.ProductKey).ToList();
var productActions = productActionsRepo.Where(x => productkeys.Contains(x.ProductKey));
You'll tie yourself in knots trying to write an SQL then "converting" it into LINQ or forcing EF to generate an SQL that is the same.. It's better to start from a place where you express what you want in high level (English) and write the LINQ for it; forget the SQL unless there's a real problem
The SQL as written doesn't really make sense, or need a CTE, the HAVING clause is pointless and none of the columns from the CTE are used in the output. The only purpose the CTE serves is to filter the product list down to those that have at least one productkey, so write an EF from that - "all products Where profile is is 100 and a related product key exists" - don't get bogged down in "how do I make EF do a cte?" because these SQL express the same sentiments without a CTE:
SELECT p.name,p.productkey,p.imageurl
FROM product p
INNER JOIN (SELECT DISTINCT productKey FROM productactions) c on p.ProductKey=c.productkey
WHERE p.profileid=100
SELECT DISTINCT p.name,p.productkey,p.imageurl
FROM product p
INNER JOIN productactions c on p.ProductKey=c.productkey
WHERE p.profileid=100
SELECT p.name,p.productkey,p.imageurl
FROM product p
WHERE p.profileid=100
AND EXISTS(SELECT null FROM productactions c WHERE p.ProductKey=c.productkey)
Assuming product and productactions are in a 1:M relationship connected by productKey, consider something like:
var products = productRepo
.Where(p => p.profileid==100 && p.ProductActions.Any())
.Select(p => new {p.Name, p.ProductKey, p.ImageUrl)
Main message here is "don't start from an SQL mindset and think "how can I make EF do this sql", start from a "What do I want and how can I make EF do it" - forget the SQL unless EF is generating something horrifically underperformant.

Using collation in Linq to Sql

Imagine this sql query
select * from products order by name collate Persian_100_CI_AI asc
Now using Linq:
product = DB.Products.OrderBy(p => p.name); // what should I do here?
How can I apply collation?
This is now possible with EF Core 5.0 using the collate function.
In your example the code would be:
product = DB.Products.OrderBy(p => EF.Functions.Collate(p.name, "Persian_100_CI_AI"));
There is no direct way.
Workaround:
Create function in Sql Server
CREATE FUNCTION [dbo].[fnsConvert]
(
#p NVARCHAR(2000) ,
#c NVARCHAR(2000)
)
RETURNS NVARCHAR(2000)
AS
BEGIN
IF ( #c = 'Persian_100_CI_AI' )
SET #p = #p COLLATE Persian_100_CI_AI
IF ( #c = 'Persian_100_CS_AI' )
SET #p = #p COLLATE Persian_100_CS_AI
RETURN #p
END
Import it in model and use:
from o in DB.Products
orderby DB.fnsConvert(s.Description, "Persian_100_CI_AI")
select o;
You can't change the collation through a LINQ statement. You better do the sorting in memory by applying a StringComparer that is initialized with the correct culture (at least... I hope it's correct) and ignores case (true).
DB.Products.AsEnumerable()
.OrderBy (x => x, StringComparer.Create(new CultureInfo("fa-IR"), true))
edit
Since people (understandably) don't seem to read comments let me add that this is answered using the exact code of the question, in which there is no Where or Select. Of course I'm aware of the possibly huge data overhead when doing something like...
DB.Products.AsEnumerable().Where(...).Select(...).OrderBy(...)
...which first pulls the entire table contents into memory and then does the filtering and projection the database itself could have done by moving AsEnumerable():
DB.Products.Where(...).Select(...).AsEnumerable().OrderBy(...)
The point is that if the database doesn't support ordering by some desired character set/collation the only option using EF's DbSet is to do the ordering in memory.
The alternative is to run a SQL query having an ORDER BY with explicit collation. If paging is used, this is the only option.

How to convert this simple Entity Framework query into a standard SQL query?

I have no experience with the .NET Entity Framework and I have some doubts about what exactly do this query:
using (MyCorpo.EarlyWarnings.Model.EarlyWarningsEntities context = new Model.EarlyWarningsEntities())
{
DBContext.SetTimeout(context);
model.VulnerabilitySeverityAverage = (from x in context.VulnerabilityAlertDocuments select x.Severity).Average();
}
(Where the type of the model.VulnerabilitySeverityAverage is simply a string)
So I think that VulnerabilityAlertDocuments map the VulnerabilityAlertDocument database table because into the EarlyWarningsEntities I have something this line:
public DbSet<VulnerabilityAlertDocument> VulnerabilityAlertDocuments { get; set; }
So I am executing a query on the VulnerabilityAlertDocuments DbSet object that represent a query on my VulnerabilityAlertDocument table on my database. Is it correct?
So what exatly do the previous query?
I think that it select the Severity field value of all records in the VulnerabilityAlertDocument table and calculate the avarage value from all these value.
Is it my reasoning correct?
How can I convert this entity query in a classic SQL query? Someone can help me?
Tnx
How can I convert this entity query in a classic SQL query?
To see actual SQL you can just call .ToString() method on your query;
var sql = (from x in context.VulnerabilityAlertDocuments select x.Severity).Average().ToString();
So I am executing a query on the VulnerabilityAlertDocuments DbSet
object that represent a query on my VulnerabilityAlertDocument table
on my database. Is it correct?
Yes
So what exatly do the previous query?
Your query will average value in Severity column of ValnerabilityAlertDocuments table.
your translated query would've looked simular to this:
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
AVG([Extent1].[Severity]) AS [A1]
FROM [dbo].[ValnerabilityAlertDocuments] AS [Extent1]
) AS [GroupBy1]
Also you could try to use such tool as SQL Server Profiler
UPDATE:
Just adding LinqPad to list of tools (thanks to Dismissile)
Select Average(x.Severity)
From VulnerabilityAlertDocuments x
Thats assuming your table is called "VulnerabilityAlertDocuments"
try again

Linq to Entities many to many selection: How to force the generation of a JOIN instead of a subselect clause?

Using EF DB first I have two entities (Supplier, Product) that have a many-to-many relationship. Entity Framework does not create an entity for the associated table (SupplierProduct) as the associated table contains only the primary keys of the strong entities.
I have been getting all Suppliers that do not supply a given product with the following query:
var q1 = context.Suppliers.Where(s=>!s.Products.Any(p=>p.Id == 1));
The SQL produced uses an EXISTS dependent subquery similar to this:
SELECT *
FROM Suppliers s
WHERE NOT EXISTS
(SELECT 1
FROM SupplierProduct sp WHERE sp.SupplierId = s.Id && sp.ProductId = 1)
Is it possible, using Linq to Entities method syntax, to produce a query that uses joins on the associated table instead?
ie:
SELECT DISTINCT s.*
FROM SupplierProduct sp
JOIN Supplier s ON s.Id = sp.SupplierId;
WHERE sp.ProductId != 1
Update
As pointed out by JoeEnos my queries above don't do the same thing. The NOT EXISTS subquery is probably the best way to go here. What if I was trying to get all suppliers who did supply a product? I would change my linq to entities query slightly to:
var q1 = context.Suppliers.Where(s => s.Products.Any(p=>p.Id == 1));
And the SQL generated would be:
SELECT *
FROM Suppliers s
WHERE EXISTS
(SELECT 1
FROM SupplierProduct sp WHERE sp.SupplierId = s.Id && sp.ProductId = 1)
Which is fine, I get the result I want. However if I was writing SQL in this case I would normally do:
SELECT s.*
FROM SupplierProduct sp
JOIN Supplier s ON s.Id = sp.SupplierId;
WHERE sp.ProductId = 1
Can my linq to entities query be changed to produce the above SQL?
To generate SQL where a join is used instead of EXISTS when selecting an entity based on its m:n association with another entity SelectMany() can be used. Eg:
var q1 = context.Suppliers.Where(s => s.Products.Any(p=>p.Id == 1));
Can be rewritten to:
var q1 = context.Products.Where(p => p.Id == 1).SelectMany(p => p.Suppliers);
Your two queries do very different things. Your Any/EXISTS query gets suppliers who do not have product 1 at all. Your JOIN query gets all suppliers who have any products other than 1, regardless of whether or not they also have product 1.
I don't think you can do what you're looking for with just a JOIN and WHERE - you can do it with an IN clause, but I think the EXISTS query is the most correct way of looking for your data.
In any case, one of the wonderful things about Entity Framework is that you don't have to worry about what gets generated - as long as the LINQ statement is ok, then it will find the best way of writing the query, and you should never have to look at it. That's especially true when you do paging and other things like that, where the LINQ is simple, but the generated SQL is horribly ugly.

Entity Framework v4.1 LIKE

How do I have to build my query to result in an output SQL query like:
SELECT
[viewRegisters].[Id] AS [IdRegister]
WHERE Name LIKE '%a%bc'
OR
SELECT
[viewRegisters].[Id] AS [IdRegister]
WHERE Name LIKE 'a%b%c'
OR
SELECT
[viewRegisters].[Id] AS [IdRegister]
WHERE Name LIKE 'a%b%c%'
I'm using .Net Framework 4.0, Entity Framework v4.1 and C#.
EF v4.1 converts this type of linq queries from:
((IQueryable<T>)Data).Where(z => z.Field.Contains("a%b%c%"));
Into:
SELECT
[viewRegisters].[Id] AS [Id]
WHERE Name LIKE N'a~%b~%c~%' ESCAPE N'~'
That's not what I want. I want to be able to use the 'percent' symbol as I do directly in DB.
You must use ESQL if you want full wildcard support. Linq-to-entities is not able to do that and EFv4.1 code first (without EDMX) doesn't have support for model defined functions so the solution provided by #Johann Blais cannot be used.
I guess the code to run ESQL query can look like:
string command = "SELECT VALUE e FROM ContextName.DbSetName AS e WHERE e.Field LIKE 'a%b%c%'"
ObjectContext ctx = ((IObjectContextAdapter)dbContext).ObjectContext;
ObjectQuery<EntityType> query = new ObjectQuery<EntityType>(command, ctx);
ObjectResult<EtntiyType> result = query.Execute(MergeOption.AppendOnly);
If you are using SQL Server, use the PATINDEX function to do a pattern search. You can access this function through EF using the SqlFunctions class.
For example, the following EF query
context.ViewRegisters.Where(z => SqlFunctions.PatIndex("a%b%c%", z.Name) > 0);
will translate into
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name]
FROM [dbo].[ViewRegisters] AS [Extent1]
WHERE (CAST(PATINDEX(N'a%b%c%', [Extent1].[Name]) AS int)) > 0
var query = from viewRegister in context.ViewRegisters
where viewRegister.Name.Contains("yourname")
select viewRegister;

Categories