web-dev-qa-db-ja.com

テーブルを作成したくない移行なしで、Entity Framework Core 3.1のカスタムオブジェクトに対して生のSQLクエリを実行するにはどうすればよいですか?

Storeテーブルをクエリして、ユーザーに最も近い10個のStoresを表示しています。 NameDistanceStoreを表示したいのですが、カスタムエンティティでは距離を保ちたいと考えています。

Storeフィールド:IdNameLatitudeLongitudeなど
StoreDtoフィールド:_Id,_ Name _,_ Distance`

これはSO回答 で、特にコメントを使用して適切な方向に進みます。ただし、DbQueryは非推奨になりました。

Keyless Entity Types のドキュメントでは、Keyless Entity Typeを使用して生の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
_

店舗検索コードを機能させます。しかし、次に移行を実行するとき、醜いToView(null)ハックを追加しない限り、EF CoreはStoreDtoテーブルを作成する必要があります。

参考までに、これが私のクエリです:

_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();
_

これを行う適切な方法は何ですか?推奨される方法を知っていると思われる場合は、出典を引用していただけますか?この投稿の時点では、キーレスエンティティタイプのドキュメントページでは、生のクエリではなく、ビューとテーブルに重点が置かれています(私が何かを見落とした場合を除きます)。

3
Chris

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

    }

使用は次のようになります。

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

Efコア3.xでDbQueryに相当するものを作成するには、モデル作成でエンティティにHasNoKey()およびToView()を追加します。これにより、移行によってテーブルが作成されなくなります。

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);
    });
}
1
Johan