简体   繁体   中英

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM