[英]How do I execute a raw SQL query to a custom object in Entity Framework Core 3.1, without migrations wanting to create a table?
I'm querying a Store
table to show the user the 10 closest Store
s.我正在查询
Store
表以向用户显示 10 个最近的Store
。 I'd like to display the Name
and Distance
of the Store
, but prefer to keep distance in a custom entity.我想显示
Store
的Name
和Distance
,但更喜欢在自定义实体中保持距离。
Store
fields: Id
, Name
, Latitude
, Longitude
, etc Store
字段: Id
、 Name
、 Latitude
、 Longitude
等StoreDto
fields: Id,
Name ,
Distance` StoreDto
字段: Id,
Name ,
Distance`
This SO answer gets us on the right track, particularly with the comments.这个 SO 答案让我们走上了正确的轨道,尤其是评论。 However, DbQuery is now deprecated.
但是,现在不推荐使用 DbQuery。
The docs on Keyless Entity Types say we can use a Keyless Entity Type to serve as the return type for raw SQL queries. Keyless Entity Types上的文档说我们可以使用 Keyless Entity Type 作为原始 SQL 查询的返回类型。
My DbContext already has:我的 DbContext 已经有:
public DbSet<Store> Stores { get; set; }
Adding添加
public DbSet<StoreDto> StoreDtos { get; set; }
And和
modelBuilder.Entity<QuestSiteDto>()
.HasNoKey()
.ToView(null); // Hack to prevent table generation
Allows my store search code to work.允许我的商店搜索代码工作。 But the next time I run a migration, EF Core wants to create a StoreDto table, unless I add that ugly
ToView(null)
hack.但是下次我运行迁移时,EF Core 想要创建一个 StoreDto 表,除非我添加那个丑陋的
ToView(null)
hack。
For reference, here is my query:作为参考,这是我的查询:
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();
What is the proper way to do this?这样做的正确方法是什么? If you believe you know the recommended way, can you please cite your source?
如果你相信你知道推荐的方式,你能引用你的来源吗? As of the time of this posting, the Keyless Entity Types doc page focuses more on Views and Tables rather than raw queries (unless I missed something).
截至本文发布时,无键实体类型文档页面更多地关注视图和表而不是原始查询(除非我遗漏了一些东西)。
You can also query types not registered in your DbContext.您还可以查询未在 DbContext 中注册的类型。 The idea is to introduce introduce a separate single-entity DbContext type for each ad-hoc query type.
这个想法是为每个 ad-hoc 查询类型引入一个单独的单实体 DbContext 类型。 Each would be initialized and cached seperately.
每个将被单独初始化和缓存。
So just add an extension method like this:所以只需添加一个像这样的扩展方法:
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);
}
}
}
Or for EF Core 5:或者对于 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);
}
}
}
Use would look like:使用看起来像:
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");
}
This originally appeared here, but github issue comment threads aren't very discoverable: https://github.com/dotnet/efcore/issues/1862#issuecomment-451671168这最初出现在这里,但 github 问题评论线程不是很容易发现: https : //github.com/dotnet/efcore/issues/1862#issuecomment-451671168
To create the equivalent of DbQuery in ef core 3.x you add HasNoKey() and ToView() to your Entity in your modelcreating.要在 ef core 3.x 中创建等效的 DbQuery,您可以在模型创建中将 HasNoKey() 和 ToView() 添加到您的实体。 This will prevent Migrations from creating a table.
这将阻止迁移创建表。
public DbSet<Store> Stores { get; set; }
public DbSet<StoreDto> StoreDtos { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<StoreDtos>(sd =>
{
sd.HasNoKey().ToView(null);
});
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.