Как выполнить необработанный запрос SQL к пользовательскому объекту в Entity Framework Core 3.1 без миграции, желающей создать таблицу? - PullRequest
3 голосов
/ 07 января 2020

Я запрашиваю таблицу Store, чтобы показать пользователю 10 ближайших Store с. Я хотел бы отобразить Name и Distance из Store, но предпочитаю сохранять расстояние в пользовательском объекте.

Store поля: Id, Name, Latitude, Longitude, et c
StoreDto поля: Id, Имя , Расстояние`

Этот SO-ответ выводит нас на правильный путь, особенно с комментариями. Однако теперь DbQuery устарел.

В документах по типам объектов без ключей говорится, что мы можем использовать тип объекта без ключей, чтобы служить типом возврата для необработанных 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) хак.

Для справки, вот мой запрос:

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

Как правильно это сделать? Если вы считаете, что знаете рекомендуемый способ, не могли бы вы привести свой источник? На момент этой публикации страница «Типы без ключа» на странице c больше фокусировалась на представлениях и таблицах, чем на необработанных запросах (если я что-то пропустил).

Ответы [ 2 ]

1 голос
/ 12 января 2020

Вы также можете запрашивать типы, не зарегистрированные в вашем DbContext. Идея состоит в том, чтобы ввести отдельный тип DbContext для одного объекта для каждого типа запроса ad-ho c. Каждый будет инициализирован и кэширован отдельно.

Так что просто добавьте метод расширения, подобный этому:

   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 не отображаются ' t очень легко обнаружить: https://github.com/dotnet/efcore/issues/1862#issuecomment -451671168

1 голос
/ 07 января 2020

Чтобы создать эквивалент DbQuery в ef core 3.x, вы добавляете 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);
    });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...