Creating SQL translations for EF Core - c#

Using ExecuteSqlInterpolated with EF Core is a great way to write more complex queries and also keep boilerplate down by doing binding on the fly with string interpolation too. A common clause we use in our code base is the IN clause.
Unfortunately when using ExecuteSqlInterpolated this is not possible because the "translation"/mapping to SQL is not implemented (FYI we are using the Pomelo library).
Here is an example:
var rowsChanged = await dbContext.Database.ExecuteSqlInterpolatedAsync($#"
UPDATE
ExampleTable
SET
ExampleColumn = true,
WHERE
Id IN ({ids})
");
This results in an error
System.InvalidOperationException : The current provider doesn't have a store type mapping for properties of type 'int[]'
To get around this, we build a custom query string and use the non-interpolated alternative, while this may still look clean it gets very messy in even a mildly complex query.
var idPlaceholders = Enumerable.Range(0, ids.Length).Select(i => $"{{{i}}}").StringJoin(", ");
var query = $#"
UPDATE
ExampleTable
SET
ExampleColumn = true,
WHERE
Id IN ({idPlaceholders})
";
var rowsChanged = await dbContext.Database.ExecuteSqlRawAsync(query, ids);
I tried doing this in DbContext ConfigureConventions, but I get the same error, it must be at a different level.
configurationBuilder
.DefaultTypeMapping<int[]>()
.HasConversion<IntArrayDbValueConverter>();
configurationBuilder
.Properties<int[]>()
.HaveConversion<IntArrayDbValueConverter>();
There must be some way to add a custom "translation" to the DbContext or provider. Any of the scarce documentation I can find on this topic has been vague or irrelevant. Looking for help.

Related

Stored Procedure Returning Data Set Under .NET Core 3.1

Using Entity Framework 6.3, I am trying to execute a stored procedure that returns a dataset (as a result of a select statement in the procedure, that joins multiple tables). The stored procedure exists and I am able to execute it via SSMS using the same credentials, just to eliminate the obvious suggestions.
The call to the stored procedure looks like this:
using Microsoft.Data.SqlClient; //!
var timestamp = new DateTime.UtcNow();
var scheduleNo = "123";
var machine = "3";
var result = await DbContext.Database.SqlQuery<ScheduleData>(
"exec GetScheduleData #UpdateTimeStamp, #ScheduleNo, #Machine",
new SqlParameter("#UpdateTimeStamp", timestamp),
new SqlParameter("#ScheduleNo", scheduleNo),
new SqlParameter("#Machine", machine))
.ToListAsync();
The execution fails with the following error:
The SqlParameterCollection only accepts non-null SqlParameter type objects, not SqlParameter objects.
I searched for this exact error, found similar question here, but I don't think it's applicable, as the original poster doesn't even use EF.
The answer to this question doesn't seem applicable, as I am already using Microsoft.Data.SqlClient (as emphasized in the listing above).
There is no DbSet defined in the DbContext that would correspond to the result set being returned from the procedure, so I can't really use DBSet<ScheduleData>.FromSql or .FromSqlRaw.
FWIW, I need the result set to be readonly, so don't need EF change tracking, etc.
Is there a way to do it without completely bypassing EF and having to create a separate DB connection?
You can use the extension method from B3.Extensions.Data. Any kind of raw sql query can execute using entity framework like following..
public async Task<IActionResult> Get()
{
var sql = "SELECT type, SUM(length) FROM ways GROUP BY type";
var queryResult = await _context.ExecuteQueryAsync(sql);
return Ok(response);
}
Ran into this issue and in my scenario using .NET Core 3.1 with EF6 I had to qualify the parameters as Microsoft.Data.SqlClient.SqlParameter.
command.Parameters.Add(new Microsoft.Data.SqlClient.SqlParameter("#param1", someString));
Kind of the opposite of the comments suggestion, at least as I read it.

Add sort on reflected navigation property

I'm using EF 6.0 .NET-Framework and MS SQL Sever and I have the follow situation: I have a dynamic data selection on a Navigation property from a given entity. This works so far OK. But: I like to add some sortings. But I cannot figure out how to make EF understand, that the sort shall be sent to the database instead sorting on client side afterwards.
The problem seems, that the data is requested from database when I retrieve the navigation property's value and not when I complete the command chain with the sort.
My code is like (simplyfied):
var dynamicRelatedEntityType = typeof(RelatedEntity);
using (var dbContext = new DBContext())
{
var orderByFunction = buildOrderByFunction(dynamicRelatedEntityType ); // this just builds a function for the order by ...
var masterEntity = dbContext.MasterEntity.first(x=> x.Whatever = true);
var navigationProperty = masterEntity.GetType().GetProperty(dynamicRelatedEntityType.Name);
var result = navigationProperty.GetValue(masterEntity).OrderBy(orderByFunction).ToList();
// result is OK, but sort wasn't sent to data base ... it was done by my program which is quite time expensive and silly too ...
}
So, how I can change this behaviour, any ideas?
Thank you in advance!
EDIT
The solution provided for this question solves to do dynamic predicates, but you cannot apply them if you still use navigationProperty.GetValue(masterEntity). In that case the EF will fire SQL immediatley without any order or where clause ...
Your database server is able to process TSQL statements only. Entity Framework (particularly the SQL Server pluging for Entity Framework) is capable of translating a small subset of C# expressions in a valid TSQL (in your case for an order by statement).
When your expression is too complex (for example invokes methods, alters the state) to be translanted into TSQL, Entity Framework will resort to an in memory operation.
If your are using .NET Core, you can use the following snipped while registering the content to spot all the "unsupported" statements that are executed in memory.
var builder = new DbContextOptionsBuilder<MyContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
// the following line is the one that prevents client side evaluation
builder.ConfigureWarnings(x => x.Throw(RelationalEventId.QueryClientEvaluationWarning));
Givem that, which is important to understand when a custom expression is involved, LINQ requires a static expression to infer the ordering. However, you can generate a dynamic expression as suggested by LINQ Dynamic Expression Generation. Although I never tried the described approach, it seems to me a viable way to achieve what you ask.

How to execute SqlQuery with Entity Framework Core 2.1?

In Entity Framework 6, I can execute a raw SQL query on the database using the following command:
IEnumerable<string> Contact.Database.SqlQuery<string>("SELECT a.title FROM a JOIN b ON b.Id = a.aId WHERE b.Status = 10");
On a new project, I am trying to use Entity Framework Core 2.1. I have a need to execute raw SQL query. While googling, I can see that the extension SqlQuery was changed to FromSql. However, FromSql only exists on the DbSet<> not on the DbContext.Database.
How can I run FromSql outside the DbSet<>? The method FromSql does not exists on the database object DbContext.Database.FromSql<>.
Updates
This is wokring for EF Core 2.1 but if you're using EF Core 3.0 or higher versions please refer to this complete answer.
I can see that the extension SqlQuery was changed to FromSql
But the new FromSql method is more restrcitive than SqlQuery. The documentation of that method explains that it exists some limitations like:
SQL queries can only be used to return entity types that are part of
your model. There is an enhancement on our backlog to enable returning ad-hoc types from raw SQL queries.
The SQL query must return data for all properties of the entity or query type.
[...]
Update: more recent GitHub dotnet/efcore discussion Support raw SQL queries without defining an entity type for the result #10753
So in your case the SQL query you're using is the following:
SELECT a.title FROM a JOIN b ON b.Id = a.aId WHERE b.Status = 10
As the documentation said you can only use FromSql with entity or query type. Your SQL query doesn't return all data of your entity defined in your model but it only returns one column of your entity. By the way a new feature is introduced in EF Core 2.1 which is in Release Candidate since 7 may 2018. Microsoft says:
EF Core 2.1 RC1 is a “go live” release, which means once you test that
your application works correctly with RC1, you can use it in
production and obtain support from Microsoft, but you should still
update to the final stable release once it’s available.
##Using FromSql on query type##
What is a query type:
An EF Core model can now include query types. Unlike
entity types, query types do not have keys defined on them and cannot
be inserted, deleted or updated (i.e. they are read-only), but they
can be returned directly by queries. Some of the usage scenarios for
query types are: mapping to views without primary keys, mapping to tables without primary keys, mapping to queries defined in the model, serving as the return type for FromSql() queries
If you want to use query type feature with your SQL text you first define a class, let's name it MySuperClass:
public class MySuperClass
{
public string Title { get; set; }
}
Then in your DbContext class defined a property of type DbQuery<MySuperClass> like below:
public DbQuery<MySuperClass> MySuperQuery { get; set; }
Finally you can use FromSql on it like below:
var result = context.MySuperQuery.FromSql("SELECT a.title FROM a JOIN b ON b.Id = a.aId WHERE b.Status = 10").ToList().First();
var title = result.Title;
##Don't want to use DbQuery<T>##
If you don't want to use DbQuery<T> and don't want to define a class that contains only one property then you can use ExecuteSqlCommandAsync like #vivek nuna did in his answer(his answer is partially correct). But you must know that returned value by that method is the number of rows affected by your query. Also you must put your title as an output parameter so make your query a stored procedure. Use ExecuteSqlCommandAsync or ExecuteSqlCommand and after that read the output parameter you passed when calling the method.
A simpler way without creating a stored procedure therefore not using ExecuteSqlCommandAsync or ExecuteSqlCommand is to the following code:
using (var context = new MyDbContext())
{
var conn = context.Database.GetDbConnection();
await conn.OpenAsync();
var command = conn.CreateCommand();
const string query = "SELECT a.title FROM a JOIN b ON b.Id = a.aId WHERE b.Status = 10";
command.CommandText = query;
var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var title = reader.GetString(0);
// Do whatever you want with title
}
}
You can make this logic a helper method that will recevie your SQL Query and returns the desired data. But I recommend you use Dapper.Net whcih contains a lot of helpers methods that will help to deal easily with RAW SQL like we do above and also sharing the smae connection with DbContext.
You can use ExecuteSqlCommandAsync method which is defined in RelationalDatabaseFacadeExtensions class of Microsoft.EntityFrameworkCore.Relational assembly by the following way.
_databaseContext.Database.ExecuteSqlCommandAsync(<Your parameters>)

Entity Framework get specific column from a string

I have a Web API project that uses Entity Framework. I have an action api/account that given an ID returns that account.
There is an additional parameter that can be provided which is what fields to be returned e.g. api/account?field=name
I'm trying to work out the best way of translating from this string input to Entity Framework. I know I can use .Select() to only get certain columns but that takes in a Func and the only way I can think of getting from the string to a Func is by using Reflection. This seems like it will be a peformance hit if I have to do Reflection for every property passed in, is there a better way of doing this?
Thanks
You can use this library System.Linq.Dynamic from Nuget, and this query
var selectStatement = string.Format("new ({0},{1})", field1, field2);
var result = db.Users.Select(selectStatement).ToListAsync().Result;

Entity framework find similar string

Is there a way how to find similar string in database via EF?
I would like to show users already existing questionts when he tries to add a new one (Implementation of exactly same feature as here on stack overflow "Questions that may already have your answer").
And order them by relevancy
well you can execute any arbitary sql and get entity framework object back.
using (var context = new myContext())
{
var myObject= context.myObject.SqlQuery("SELECT * FROM dbo.myObject where 'super duper where statement'").ToList();
}
And you can use full text search and all sorts of strange mechanisms in your sql statements.
look at this answer for further info
I believe stackoverflow is currently using the popular elasticsearch engine to index the entries and provide this functionality and it would be pretty cumbersome to try and implement this using only EF (if at all possible). However you still can use the normal string methods (StartsWith, Contains, etc) for providing some somewhat complex search functionality other than the simple matching of strings. For example:
var searchResult = Context.Products.All(x => x.Name.Contains("searchTerm"));

Categories

Resources