How can I call a stored procedure in EF Core on entity with query filters? - entity-framework-core

We are using Query Filters in EF Core 7 to handle soft-deleted records. Unfortunately, when I call a stored procedure using .FromSql("mysproc"), it gives me the following error...
InvalidOperationException: 'FromSql' or 'SqlQuery' was called with non-composable SQL and with a query composing over it. Consider calling 'AsEnumerable' after the method to perform the composition on the client side.
This TextValues.IgnoreQueryFilters().FromSql($"MySproc {myparam}") doesn't work because FromSql() is not defined on IQueryable (the code does not compile).
This MyObjects.FromSql("MySproc").IgnoreQueryFilters() compiles, but produces the same error. I'm pretty sure that IgnoreQueryFilters() needs to be first.
The stored procedure does return data for MyObject, so I would like to use the MyObjects property, if possible.
Currently, my only idea is to add a 2nd property without a filter, but this isn't ideal. I would prefer not making an unfiltered property available.
Any other ideas?

MyObjects.FromSql("MySproc").IgnoreQueryFilters() should work.
Here's a compete example with EF Core 7.0.3
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations.Schema;
using var db = new Db();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Database.ExecuteSql($"create proc get_foo #n varchar(20) as select * from Foo");
var a = "a";
var c1 = db.Set<Foo>().FromSql($"exec get_foo {a}").IgnoreQueryFilters().ToList();
var c2 = db.Set<Foo>().ToList();
Console.WriteLine("Finished");
class Db : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(LocalDb)\\MSSQLLocalDB;Database=EfCore7Test;TrustServerCertificate=true;Integrated Security=true",
o =>
{
o.UseRelationalNulls();
})
.LogTo(m => Console.WriteLine(m), LogLevel.Information);
optionsBuilder.EnableSensitiveDataLogging();
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Foo>().HasQueryFilter( e => e.Name == "foo");
}
}
[Table("Foo")]
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
outputs
info: 2/16/2023 18:54:00.134 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (23ms) [Parameters=[p0='a' (Size = 4000)], CommandType='Text', CommandTimeout='30']
exec get_foo #p0
info: 2/16/2023 18:54:00.203 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [f].[Id], [f].[Name]
FROM [Foo] AS [f]
WHERE [f].[Name] = N'foo'
Finished

Related

How do I map a MySql user defined function with ASP.NET EF Core Pomelo

I'm trying to map a custom MySQL function to a method on my dbcontext as shown in this article. As suggested in the article I've stubbed out a method with the same signature of my user-defined function.
public int CalcOffset(DateTime input_date, int id)
=> throw new InvalidOperationException();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDbFunction(typeof(DbContext).GetMethod(nameof(CalcOffset), new[] { typeof(DateTime), typeof(int) }),
b =>
{
b.HasName("fn_test");
b.HasParameter("input_date");
b.HasParameter("id");
});
}
The MySql function I'm attempting to map to:
CREATE DEFINER=`myuser`#`localhost` FUNCTION `fn_test`(input_date datetime, project_id int) RETURNS int(11)
BEGIN
RETURN 1;
END
finally I call the function via the context
var offset = _context.CalcOffset(DateTime.Now, id);
When I run the code above I'm getting the error message thrown by the function I stubbed out. However per the article the method should be mapped the database function, not the actual method:
The body of the CLR method is not important. The method will not be
invoked client-side, unless EF Core can't translate its arguments. If
the arguments can be translated, EF Core only cares about the method
signature
Versions:
ASP.NET Core 3.1
Pomelo Entity Framework Core 3.2
MySql 5.7
UDFs work fine in my tests. Here is a fully working console program:
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Pomelo.EntityFrameworkCore.MySql.Storage;
namespace IssueConsoleTemplate
{
public class IceCream
{
public int IceCreamId { get; set; }
public string Name { get; set; }
}
public class Context : DbContext
{
public virtual DbSet<IceCream> IceCreams { get; set; }
public int IncrementInteger(int value)
=> throw new InvalidOperationException();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connectionString = "server=127.0.0.1;port=3306;user=root;password=;database=So66857783";
optionsBuilder.UseMySql(
connectionString,
options => options.CharSetBehavior(CharSetBehavior.NeverAppend)
.ServerVersion(ServerVersion.AutoDetect(connectionString)))
.UseLoggerFactory(
LoggerFactory.Create(
configure => configure
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IceCream>(
entity =>
{
modelBuilder.HasDbFunction(
typeof(Context).GetMethod(nameof(IncrementInteger)),
b => b.HasName("udf_increment")
.HasParameter("value"));
entity.HasData(
new IceCream {IceCreamId = 1, Name = "Vanilla"},
new IceCream {IceCreamId = 2, Name = "Chocolate"},
new IceCream {IceCreamId = 3, Name = "Matcha"}
);
});
}
}
internal static class Program
{
private static void Main(string[] args)
{
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
SetupDatabase(context);
var iceCreams = context.IceCreams
.OrderBy(i => i.IceCreamId)
.Select(
i => new
{
IceCreamId = i.IceCreamId,
IcrementedValue = context.IncrementInteger(i.IceCreamId)
})
.ToList();
Trace.Assert(iceCreams.Count == 3);
Trace.Assert(iceCreams[0].IceCreamId == 1);
Trace.Assert(iceCreams[0].IcrementedValue == 2);
}
private static void SetupDatabase(DbContext context)
{
context.Database.OpenConnection();
var connection = context.Database.GetDbConnection();
using var command = connection.CreateCommand();
command.CommandText = #"CREATE FUNCTION `udf_increment`(value int) RETURNS int
DETERMINISTIC
BEGIN
RETURN value + 1;
END";
command.ExecuteNonQuery();
}
}
}
It generates the following SQL query:
SELECT `i`.`IceCreamId`, `udf_increment`(`i`.`IceCreamId`) AS `IcrementedValue`
FROM `IceCreams` AS `i`
ORDER BY `i`.`IceCreamId`
However, just calling the function without an entity context, as you do in your OP, will not work because you would just execute your function body, instead of letting EF Core translating an expression tree to SQL:
// This does not work, because Context.IncrementInteger(int) is being directly called
// here, instead of being part of an expression tree, that is then translated to SQL
// by EF Core.
var answer = context.IncrementInteger(41);
Trace.Assert(answer == 42);
Of course you can always execute some good old SQL yourself, if you need to:
// Executing the UDF using god old SQL.
// Use parameters for input values in the real world.
context.Database.OpenConnection();
using var command = context.Database.GetDbConnection().CreateCommand();
command.CommandText = #"SELECT `udf_increment`(41)";
var answerToEverything = (int)command.ExecuteScalar();
Trace.Assert(answerToEverything == 42);

Stop empty strings at the database level with EF code first

Consider the following POCO entity for Entity Framework Code First:
public class Foo
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
}
Which will generate the following table:
CREATE TABLE [dbo].[Foo] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (100) NOT NULL,
CONSTRAINT [PK_dbo.Foo] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Now, I understand that the default behavior of EF is to convert empty strings to null. So even if I explicitly feed it an empty string I will get a validation exception, which is perfect. The following code will throw a DbEntityValidationException:
var f = new Foo { Name = "" };
context.Foos.Add(f);
context.SaveChanges();
But, the problem is if I have an external application which accesses the database directly, I can perform the following query and it succeeds:
insert into dbo.Foo(Name)
values ('')
The best solution is arguably to not allow anyone to connect directly to the database and force them through a business layer. In reality however this may not always be possible. Especially if, say, I myself am importing external data via an SSIS package.
My best understanding says that applications should be set up to reject as much bad data at the lowest level possible. In this case this would mean the at database level. So if were creating the database the old fashioned way, I would add a constraint to check (Name <> '') and stop dirty data from ever being inserted in the first place.
Is there a way to get EF Code First to generate this constraint for me, or some other way to get it to enforce a non-empty-string (minimum length 1) at the database level - preferably using an attribute? Or is my only recourse to add the constraint manually in a migration?
There is MinLength attribute but it does not enforce the constraint on database level, you should add this constraint using migration I think.
public partial class test : DbMigration
{
public override void Up()
{
Sql("ALTER TABLE [dbo].[YOUR_TABLE] ADD CONSTRAINT " +
"[MinLengthConstraint] CHECK (DATALENGTH([your_column]) > 0)");
}
public override void Down()
{
Sql("ALTER TABLE [dbo].[YOUR_TABLE] DROP CONSTRAINT [MinLengthConstraint]");
}
}
You can add sql code generators for EF to generate these codes for MinLength attribute, I'll give you a simplified hint here:
First mark properties with MinLength
public class Test
{
public int Id { get; set; }
[MinLength(1)]
public string Name { get; set; }
}
Add MinLenghtAttribute to conventions and provide the value, which is the Length :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<MinLengthAttribute, int>(
"MinLength",
(property, attributes) => attributes.Single().Length));
}
the generated code for migration will be:
CreateTable(
"dbo.Tests",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(
annotations: new Dictionary<string, AnnotationValues>
{
{
"MinLength",
new AnnotationValues(oldValue: null, newValue: "1")
},
}),
})
.PrimaryKey(t => t.Id);
Override the SqlServerMigrationSqlGenerator to use this convention in order to generate the constraint sql code:
public class ExtendedSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(AddColumnOperation addColumnOperation)
{
base.Generate(addColumnOperation);
AddConstraint(addColumnOperation.Column, addColumnOperation.Table);
}
protected override void Generate(CreateTableOperation createTableOperation)
{
base.Generate(createTableOperation);
foreach (var col in createTableOperation.Columns)
AddConstraint(col, createTableOperation.Name);
}
private void AddConstraint(ColumnModel column, string tableName)
{
AnnotationValues values;
if (column.Annotations.TryGetValue("MinLength", out values))
{
var sql = string.Format("ALTER TABLE {0} ADD CONSTRAINT " +
"[MinLengthConstraint] CHECK (DATALENGTH([{1}]) >= {2})"
,tableName, column.Name, values.NewValue);
Generate(new SqlOperation(sql));
}
}
}
the code above contains generation for AddColumn and CreateTable operations you must add codes for AlterColumn, DropTable and DropColumns as well.
Register the new code generator:
internal sealed class Configuration : DbMigrationsConfiguration<TestContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("System.Data.SqlClient", new ExtendedSqlGenerator());
}
}

Entity framework 6 connecting multiple database implemention advice

I am a novice when it comes to entity framework.I usually use ado.net because is faster than any ORM and the repetitive code can be easily generated.
Now I have decided to give EF6 a go again and gain some experience in EF
Scenario.
Need to migrate data for many clients.(30 databases)
Each client will have their own staging database
Each database will have different tables
All databases will have/share the same "Views" schema.
Basically we decided that regardless of the clients tables ,they must all share the same Views.
So when we read the data we dont care because the views columnNames will be the same for them all.
Ado.net Implementation
Is very simple.I my dal i have methods like "GetCustomers","GetAccounts" etc... and all I need to do is change the connectionString
and I can read the views from any database.Does not get simpler than this.
EF implementation
Please correct me if I am wrong.
In order for EF to work I would have to generate code for 30 databases (databaseFirst).
Is there a way I can use just a connection string to connect to the right database and based on that connection string read data from the views?
How would you do it using EF6 by just changing the connection string?
Can you give a noddy example how to do it?
Any suggestions
here it is, just a bit more of 10 minutes... (EF 5 on .net 4.5)
using System;
using System.Linq;
using System.Data.Entity;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.Data.Objects.SqlClient;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;
namespace testef {
public class EntityZ {
public Int32 Id { get; set; }
public String P1 { get; set; }
}
public class EntityZConfiguration : EntityTypeConfiguration<EntityZ> {
public EntityZConfiguration()
: base() {
ToTable("v", "dbo"); // the view
HasKey(x => x.Id); //required
//the mapping of properties
Property(x => x.Id).HasColumnName("id");
Property(x => x.P1).HasColumnName("value");
}
}
public class TestEFContext : DbContext {
public DbSet<EntityZ> Entities { get; set; }
public TestEFContext(String cs)
: base(cs) {
//Database.SetInitializer<TestEFContext>(new DropCreateDatabaseAlways<TestEFContext>());
Database.SetInitializer<TestEFContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new EntityZConfiguration());
}
}
class Program {
static void Main(string[] args) {
//creating the db
using (SqlConnection conn = new SqlConnection("Data Source=ALIASTVALK;Initial Catalog=master;Integrated Security=True; MultipleActiveResultSets=True")) {
conn.Open();
using (SqlCommand com = conn.CreateCommand()) {
com.CommandText = "declare #v int = 0 select #v = 1 from sys.databases where name = 'TestEF' if #v = 1 drop database TestEF";
com.ExecuteNonQuery();
com.CommandText = "create database TestEF";
com.ExecuteNonQuery();
com.CommandText = "use TestEF";
com.ExecuteNonQuery();
com.CommandText = "create table t (i int not null, t nvarchar(max))";
com.ExecuteNonQuery();
com.CommandText = "insert into t (i, t) values (1, 'hello world')";
com.ExecuteNonQuery();
com.CommandText = "create view v as select i as id, t as value from t";
com.ExecuteNonQuery();
}
}
String cs = #"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";
using (TestEFContext ctx = new TestEFContext(cs)) {
foreach (EntityZ z in ctx.Entities) {
Console.WriteLine("{0}: {1}", z.Id, z.P1);
}
}
}
}
}

How can I define a stored procedure in Entity Framework (code first)?

I define my model in Entity Framework (code first), but how can I define a stored procedure in my model? I want to create my model and have it generate the stored procedure in my database when my database is generated from my model.
Description
There is no built in direct mapping support for stored procedures in Entity Framework code first at the moment. But you can exec any sql query (create your stored procedure) like in my sample.
Sample
public class MyDatabaseContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
DbCommand cmd = Database.Connection.CreateCommand();
cmd.CommandText = "create stored procedure ....";
cmd.ExecuteNonQuery();
}
}
More Information
MSDN - DbCommand Class
I'm using Entity Framework 6 so things may have changed. The database is not created in OnModelCreating, but it did work in the DbInitializer like this...
public class MyDbInitializer : DropCreateDatabaseIfModelChanges<MyDbContext> {
protected override void Seed(MyDbContext context) {
string sql = #"CREATE PROCEDURE...";
context.Database.ExecuteSqlCommand(sql);
base.Seed(context);
}
}

EF 4.1, POCO: Is any way to get Table name in runtime to avoid hardcode?

I use POCO in Entity Framework. Is any direct or indirect way in the latest EF version to get Table name at the runtime to avoid hardcode values?
I need it inside my custom database initializer to run code like this:
context.Database.ExecuteSqlCommand(
string.Format("DBCC CHECKIDENT ({0}, RESEED, {1})", tableName, newSeed))
Thanks
I'm working from the assumption that your context looks something like mine, with each of the table names getting generated from the class names when you add a DbSet to your context. If that's the case, you can achieve your goal with reflection, though it's a little ugly:
public class MyContext : DbContext
{
public MyContext() : base("MyDatabase")
{
}
public DbSet<Video> Video { get; set; }
public DbSet<VideoRating> Rating { get; set; }
public DbSet<User> User { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public class Initializer : IDatabaseInitializer<DashVaultContext>
{
public void InitializeDatabase(MyContext context)
{
if (!context.Database.Exists())
{
context.Database.Create();
PropertyInfo[] propertyInfos = typeof(MyContext).GetProperties(BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.Instance);
var newSeed = 1000; // Or whatever is appropriate
foreach (PropertyInfo propertyInfo in propertyInfos)
{
var tableName = propertyInfo.PropertyType.GetGenericArguments()[0].Name;
context.Database.ExecuteSqlCommand(
string.Format("DBCC CHECKIDENT ({0}, RESEED, {1})", tableName, newSeed));
}
}
}
}
}
UPDATE: I removed the pluralization hack and just turned off pluralization in the generated table names (see the OnModelCreating override).
POCO means you can use "plain-old" CLR objects (POCO), such as existing domain objects, with your data model. These POCO data classes (also known as persistence-ignorant objects), which are mapped to entities that are defined in a data model and by definition it shouldn't be directly related to database implementation details. However, you can use constant class and Fluent mapping to facilitate your requirement in a better way
Your constant class implementation
public static class Constant
{
public const string CreditCustomer = "dbo.CreditCustomer";
}
Your mappings goes like this
builder.Entity<Customer>()
.HasKey(c => c.ID)
.MapSingleType(c => new {
cid = c.ID,
nme = c.Name
}
)
.ToTable(Constant.Table.CreditCustomer);
In your dbInitializer
context.Database.ExecuteSqlCommand(
string.Format("DBCC CHECKIDENT ({0}, RESEED, {1})", Constant.Table.CreditCustomer, newSeed))
Looking at how "active" this discussion is, it seems to me this functionality is just not provided in the current version of EF. I hope this features will be available in one of future version of EF.
Will this code be useful at all?
var query = from meta in context.MetadataWorkspace.GetItems(DataSpace.SSpace)
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
let properties = meta is EntityType ? (meta as EntityType).Properties : null
select new
{
TableName = (meta as EntityType).Name,
Fields = from p in properties
select new
{
FielName = p.Name,
DbType = p.TypeUsage.EdmType.Name
}
};