c# - 如何在 Entity Framework Core 3.1 中对自定义对象执行原始 SQL 查询,而不需要创建表的迁移?

标签 c# entity-framework asp.net-core entity-framework-core

我正在查询 Store 表以向用户显示 10 个最近的Store。我想显示 Store名称距离,但更喜欢在自定义实体中保持距离。

Store 字段:IdNameLatitudeLongitude
StoreDto 字段:Id、名称距离`

This SO answer让我们走上正轨,尤其是评论。不过,DbQuery 现已弃用。

Keyless Entity Types 上的文档假设我们可以使用无键实体类型作为原始 SQL 查询的返回类型。

我的 DbContext 已经有:

public DbSet<Store> Stores { get; set; }

添加

public DbSet<StoreDto> StoreDtos { get; set; }

还有

modelBuilder.Entity<QuestSiteDto>()
    .HasNoKey()
    .ToView(null); // Hack to prevent table generation

允许我的商店搜索代码工作。但下次我运行迁移时,EF Core 想要创建一个 StoreDto 表,除非我添加那个丑陋的 ToView(null) hack。

作为引用,这是我的查询:

var sql = 
@"select 
    geography::Point({0}, {1}, 4326).STDistance(geography::Point(Latitude, Longitude, 4326)) / 1609.34 as Distance,
    Id,
    [Name]
from
    Store"

var results = await StoreDtos
    .FromSqlRaw(sql, latitudeUnsafe, longitudeUnsafe)
    .OrderBy(x => x.Distance)
    .Take(10)
    .ToListAsync();

执行此操作的正确方法是什么?如果您相信您知道推荐的方式,您可以引用您的来源吗?截至本文发布时,无键实体类型文档页面更多地关注 View 和表,而不是原始查询(除非我错过了一些内容)。

最佳答案

您还可以查询未在 DbContext 中注册的类型。这个想法是为每个即席查询类型引入一个单独的单实体 DbContext 类型。每个都将单独初始化和缓存。

所以只需添加一个像这样的扩展方法:

   public static class SqlQueryExtensions
    {
        public static IList<T> SqlQuery<T>(this DbContext db, Func<T> targetType, string sql, params object[] parameters) where T : class
        {
            return SqlQuery<T>(db, sql, parameters);
        }
        public static IList<T> SqlQuery<T>(this DbContext db, string sql, params object[] parameters) where T : class
        {

            using (var db2 = new ContextForQueryType<T>(db.Database.GetDbConnection()))
            {
                return db2.Query<T>().FromSql(sql, parameters).ToList();
            }
        }


        class ContextForQueryType<T> : DbContext where T : class
        {
            DbConnection con;

            public ContextForQueryType(DbConnection con)
            {
                this.con = con;
            }
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                //switch on the connection type name to enable support multiple providers
                //var name = con.GetType().Name;

                optionsBuilder.UseSqlServer(con);

                base.OnConfiguring(optionsBuilder);
            }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                var t = modelBuilder.Query<T>();

                //to support anonymous types, configure entity properties for read-only properties
                foreach (var prop in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public ))
                {
                    if (!prop.CustomAttributes.Any(a => a.AttributeType == typeof(NotMappedAttribute)))
                    {
                        t.Property(prop.Name);
                    }
                    
                }
                base.OnModelCreating(modelBuilder);
            }
        }

    }

或者对于 EF Core 5:

public static class SqlQueryExtensions
{
    public static IList<T> SqlQuery<T>(this DbContext db, Func<T> targetType, string sql, params object[] parameters) where T : class
    {
        return SqlQuery<T>(db, sql, parameters);
    }
    public static IList<T> SqlQuery<T>(this DbContext db, string sql, params object[] parameters) where T : class
    {

        using (var db2 = new ContextForQueryType<T>(db.Database.GetDbConnection()))
        {
            return db2.Set<T>().FromSqlRaw(sql, parameters).ToList();
        }
    }


    class ContextForQueryType<T> : DbContext where T : class
    {
        DbConnection con;

        public ContextForQueryType(DbConnection con)
        {
            this.con = con;
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //switch on the connection type name to enable support multiple providers
            //var name = con.GetType().Name;

            optionsBuilder.UseSqlServer(con);

            base.OnConfiguring(optionsBuilder);
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            var t = modelBuilder.Entity<T>().HasNoKey();

            //to support anonymous types, configure entity properties for read-only properties
            foreach (var prop in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (!prop.CustomAttributes.Any(a => a.AttributeType == typeof(NotMappedAttribute)))
                {
                    t.Property(prop.Name);
                }

            }
            base.OnModelCreating(modelBuilder);
        }
    }

}

使用如下:

using (var db = new Db())
{
    var results = db.SqlQuery<ArbitraryType>("select 1 id, 'joe' name");
    //or with an anonymous type like this
    var results2 = db.SqlQuery(() => new { id =1, name=""},"select 1 id, 'joe' name");
}

这最初出现在这里,但 github 问题评论线程不太容易发现:https://github.com/dotnet/efcore/issues/1862#issuecomment-451671168

关于c# - 如何在 Entity Framework Core 3.1 中对自定义对象执行原始 SQL 查询,而不需要创建表的迁移?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59620619/

相关文章:

C# - 在 Datagrid 上执行选定的日期从和到

c# - 使用 Entity Framework Core 访问 MySql

c# - 使用 ASP.net 核心 web api 发送多个文件

c# - 从azure blob下载文件并出现空pdf页面

c# - wpf、c#、viewport3D 的 renderTargetBitmap 没有将其分配给窗口

c# - 从嵌套类中的 Controller 中的 DynamicObject 继承的对象无法在 ActionMethod 中创建属性

c# - 如何将复选框值绑定(bind)到整数列表?

entity-framework - 使用 Entity Framework Code First 更新数据库架构

xml - 将 (Sql Server) xml 列与 Entity Framework 一起使用

c# - 在 Entity Framework 6 中对 ICollections 进行排序