Can Entity Framework Core run non-query calls? - c#

Unfortunately my EF application has to call stored procedures I am unable to change. While this is not ideal I can normally get around it. However, I have a stored proc that does return anything. How does EF core deal with this? I know in previous versions you could run ExecuteNonQuery but I haven't been able to find anything similar in EF Core.
I normally run my queries through a helper as such, where T is a class that maps to a return type EF can serialize to:
context.Set<T>()
.AsNoTracking()
.FromSql(query, args)
.ToListAsync();
However it looks like Set always requires a type as does .Query. Nothing else I've seen off of context would allow you to make a non-queryable call. Am I missing something?
I am using Microsoft.EntityFrameworkCore: 1.2.0

You can use the DbContext.DatabaseExecuteSqlCommand method
using(var context = new SampleContext())
{
var commandText = "INSERT Categories (CategoryName) VALUES (#CategoryName)";
var name = new SqlParameter("#CategoryName", "Test");
context.Database.ExecuteSqlCommand(commandText, name);
}
Or you can revert to ADO.NET calls off the Context:
using (var context = new SampleContext())
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "DELETE From Table1";
context.Database.OpenConnection();
command.ExecuteNonQuery();
}

Related

How to run a stored procedure in EF Core 3.14

I am working on the security and user management of a new platform built entirely in .NET Core.
In particular I am trying to generate a random password for new users. I have loaded a large list of English words into a table and created a stored procedure to select random words from the table and compose a password in the correct-horse-battery-staple format.
The stored procedure (Passwords.GenerateRandomPassword) takes no parameters and returns a single line varchar column named password.
Everything works up to this point. I can run the query directly against the server and it works fine.
I have a method on my userRepository like so:
public async Task<string> GenerateRandomPassword()
{
}
but I cannot figure out how to get EF Core 3.14 to call this stored procedure and return a value.
Documentation may not be up to date, or maybe I'm missing an assembly reference.
The context object and the context.database object do not seem to contain any methods that look like they will allow me to execute a stored procedure and retrieve a value.
Documentation seems to suggest that there should be a FromSQL method or similar.
Any help will be greatly appreciated.
The general solution is to call db.Database.GetDbConnection(), which gives you the ADO.NET connection object that you can use directly.
eg
var con = (SqlConnection)db.Database.GetDbConnection();
con.Open();
var cmd = con.CreateCommand();
cmd.CommandText = "exec ...";
There's also the db.Database.ExecuteSqlxxx methods, which work for simple cases.
What you want to look at is keyless entity types.
This is a new feature in EF Core 3.0.
One usage is to retrieve data from raw sql queries.
Using PostgreSQL.
In PostgreSQL we create a function to generate a password:
CREATE OR REPLACE FUNCTION generate_password() RETURNS text AS $$
BEGIN
RETURN (SELECT substring(md5(random()::text) from 0 for 12));
END
$$ LANGUAGE plpgsql;
We create our entity:
public class PasswordGenerator
{
public string Password { get; set; }
}
In our application's DbContext we configure our entity:
public DbSet<PasswordGenerator> PasswordGenerator { get; set; }
public MyDbContext(DbContextOptions options)
: base(options)
{}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PasswordGenerator>().HasNoKey();
}
We use the FromSqlRaw method to call our function that returns our password:
public async Task<string> GetGeneratedPassword()
{
var pg = await _context.PasswordGenerator
.FromSqlRaw(#"SELECT * from generate_password() AS ""Password""")
.FirstAsync();
return pg.Password;
}
We use the alias "Password" to correctly map our query to our entity.
The two packages installed are: Microsoft.EntityFrameworkCore and Npgsql.EntityFrameworkCore.PostgreSQL.
Using SQL Server.
We create a stored procedure to generate a password:
CREATE OR ALTER PROCEDURE generate_password
AS
SET NOCOUNT ON
SELECT SUBSTRING (CONVERT(varchar(255), NEWID()), 0, 12) AS "Password"
RETURN
GO
Use the alias "Password" to correctly map our query to our entity.
Here's how we use the FromSqlRaw method:
public async Task<string> GetGeneratedPassword()
{
var pg = (await _context.PasswordGenerator
.FromSqlRaw("EXEC generate_password")
.ToListAsync())
.First();
return pg.Password;
}
LINQ queries expect our raw queries to be composable, which is why we call ToListAsync() right after the FromSqlRaw method.
SQL Server doesn't allow composing over stored procedure calls, so any
attempt to apply additional query operators to such a call will result
in invalid SQL. Use AsEnumerable or AsAsyncEnumerable method right
after FromSqlRaw or FromSqlInterpolated methods to make sure that EF
Core doesn't try to compose over a stored procedure.
The two packages installed are: Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.SqlServer
UPDATE - Using the GetDbConnection method
Thanks to the answer provided by #David Browne - Microsoft, we can call the GetDbConnection extension method to access the database directly.
public async Task<string> GetGeneratedPassword()
{
var password = "";
var connection = _context.Database.GetDbConnection();
try
{
await connection.OpenAsync();
using (var command = connection.CreateCommand())
{
// SQL Server
command.CommandText = "EXEC generate_password";
// PostgreSQL
// command.CommandText = #"SELECT * from generate_password()";
using (var reader = await command.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
password = reader.GetString(0);
}
}
}
}
finally
{
await connection.CloseAsync();
}
return password;
}

C# LINQ for Npgsql

I have been searching the internet extensively and have found no clear path for using LINQ queries on Npgsql Objects. I want to get away from using the laborious:
using (var connection = new NpgsqlConnection(ConnectionString))
{
using (var adapter = new NpgsqlDataAdapter(query, connection))
{
connection.Open();
var command = adapter.SelectCommand;
command.Parameters.Add(param1);
command.Parameters.Add(param2);
var dataSet = new DataSet();
adapter.Fill(dataSet);
//... Do something with the dataset
}
}
I would love it if I could do something LINQ-esque like:
var pendingBalances = from c in customers
where c.Balance > 100
select new { c.FirstName, c.LastName };
Not only is it easier/shorter to write, it is way easier to read for debugging and helping future developers who have to read it.
Please tell me this is possible? If so, what object do I use in the place of "customers" to run the query against? It needs to be some sort of DbContext right? Thanks for the help!
You should use EF Core or EF 6 ORM that have Npgsql providers.
Here's a link

Non-Stored Procedure Query (CommandType=Text) with Dapper and parameters

I am trying to call a piece of SQL with parameters in Dapper. It is NOT a stored procedure (I have that working fine with parameters)
inputCustomerName = "Our Customer";
inputStationName = Null;
var parameters = new
{
customerName = inputCustomerName ,
stationName = inputStationName
};
...
using (var dbConn = dataProvider.CreateConnection)
{
dbConn.ConnectionString = connectionString;
dbConn.Open();
returnValue = dbConn.Query<T>(sql: sql, commandType: commandType, param: parameters);
dbConn.Close();
}
The relevant part of the SQL is
" ...
WHERE
Customer = ISNULL(#customerName,Customer)
AND Station = ISNULL(#stationName,Station)
";
I keep getting "Invalid type owner for DynamicMethod". (I got this also when using DynamicParameters instead of the anomymous object).
The SQL runs fine on the database itself (given I declare and #populate #customerName and stationName).
I suspect I have done something quite simple wrong - can anyone enlighten me?
The answer was, in the end, an issue in code not included in the question - for which I thoroughly apologise.
The issue was that T was an Interface, and that was what was causing Dapper to blow up. There's probably something under the hood that means you can only use classes, not interfaces as the type param to Query. Though I have never seen that restriction explicitly stated.
Shame, (but I can imagine a few reasons why)

Entity Framework naming queries

We're analyzing Azure "Query Performance Insight" to look for expensive queries, the problem is that, there is no way to relate SQL generated vs Entity Framework query.
Is there any extension method or anything else to do something like this:
SQL generated:
-- BlahMethod
SELECT Id
FROM Table1
Entity Framework cmd:
Context.Table1.Naming("BlahMethod").ToList()
Or even better:
Context.Table1.ToList() // intercept sql generated by EF and put through reflection the Method and Namespace "MyAssembly.Foo.MyMethodName"
SQL Generated:
-- MyAssembly.Foo.MyMethodName
SELECT Id
FROM Table1
Yes, look at this article Logging and Intercepting Database Operations.
It can be as simple as using Console.Write:
using (var context = new BlogContext())
{
context.Database.Log = Console.Write;
// Your code here...
}
Or you can use a log class:
using (var db = new MyDBContext())
{
db.Database.Log = s => Log.TraceVerbose("DB Context:{0}", s);
...

Open a connection to DB using EF to execute adhoc query

I'm an asp.net guy hard-core and EF + MVC = ME + WTF.
I have a task to open a connection to each of our production database servers. Run a simple query "Select top 1 * from t1 where f1 > 1".
I thought I could just use system.data.sqlclient build the conn string, open the conn and execute a query.
That doesn't seem to be working. Each connection takes forever.
How would I get an instance of our db object to do this with EF. I've seen tons of dbcontext examples but I don't even know how to get that and what it is.
I need to connect to 20 seperate DB1,TBL1,FLD1 and execute the query above. If they all succeed return an int 200 to an MVC view if even one fails just return a 503.
Thanks!
You can get a reference to the underlying DbConnection in your EF using Database.Connection.
For example:
var dbConn = context.Database.Connection;
var cmd = dbConn.CreateCommand();
cmd.CommandText = "SELECT TOP 1 * from t1 WHERE f1 > 1";
var results = cmd.ExecuteReader();
More on Raw SQL Queries with Entity Framework
From Microsoft doc - Sending Raw Commands To The Database
A- If you do not know the entity
using (var context = new BloggingContext())
{
var blogNames = context.Database.SqlQuery<string>(
"SELECT Name FROM dbo.Blogs").ToList();
}
B- If you know the entity
using (var context = new BloggingContext())
{
var blogs = context.Blogs.SqlQuery("SELECT * FROM dbo.Blogs").ToList();
}

Categories