REST-Api с сервером Entity Framework CORE / MS SQL: вычисление и возврат дополнительного поля / атрибута во время выполнения - PullRequest
0 голосов
/ 06 апреля 2020

Вводные и примерные данные

Прежде всего, я хочу извиниться за неопределенный заголовок c. Поскольку я потратил много времени на решение этой проблемы (Google, SO, пробная версия и ошибка), я пришел к тому, что даже не знаю, близок ли я к решению или нет.
Моя основная проблема c: я хочу создать REST-Api с. NET Ядром, которое возвращает объекты на определенном расстоянии от пользователя.

SQL

Предположим, у меня есть таблица с именем Музеи , которая имеет следующие столбцы:


+------------+---------------+
|    name    |     type      |
+------------+---------------+
| Id         | int           |
| MuseumName | nvarchar(max) |
| Location   | geography     |
+------------+---------------+

Модель

Принадлежность. NET Основная модель выглядит следующим образом:

    public class MuseumModel
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        public string MuseumName { get; set; }

        [JsonIgnore]
        public Geometry Location { get; set; }
    }

Проблема

Теперь я хочу создать REST-Api, который возвращает музеи в зависимости от местоположения пользователя и максимального расстояния. Давайте предположим, что вызов API такой: Museums/ByLocation?Latitude=50.114034&Longitude=8.679355&distance=1000. Элементы, возвращаемые с JSON, должны содержать Id , MuseumName и Distance . В дополнение к этому, они должны быть упорядочены по расстоянию .

Основная проблема : Где я должен вычислить эти данные о местоположении. На мой взгляд, есть 3 теоретических варианта, в которых можно выполнять вычисления:

  • SQL
  • REST-Api
  • Native Client (на мой взгляд это на самом деле это не вариант, потому что мне нужно было бы сначала перенести все музеи)

До сих пор я пытался выполнять вычисления с сервером SQL. Есть ли какие-либо аргументы против этого?

Идея A (logi c на контроллере, вычисления на БД)

Прежде всего я попытался решить проблему в контроллере REST-Api:

var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
Geometry userLocation = geometryFactory.CreatePoint(new Coordinate(longitude, latitude));

return await _context.Museums
       .Where(x => x.Location.IsWithinDistance(userLocation, distance))
       .OrderBy(x => x.Location.Distance(userLocation))
       .ToListAsync();

Задача A.1) : Я спрашиваю себя, является ли здесь оптимальным время вычислений, поскольку IsWithinDistance и Distance вызываются отдельно. Оптимизирует ли сервер SQL этот запрос?
Проблема A.2) : Как я могу легко добавить Distance к полученному JSON? В этом примере расстояние выбрасывается после вычисления.

Идея B (хранимая процедура, вызываемая контроллером)

Мой второй подход был хранимой процедурой на сервере SQL, которая в основном добавляет поле DistanceToUser для оператора SELECT. SP выглядит следующим образом:

CREATE PROCEDURE [dbo].[MuseumsWithDistanceToUser]
    @latitude float,
    @longitutde float
AS
    DECLARE @userLocation geography;
    SET @userLocation = geography::STGeomFromText('POINT('+CONVERT([varchar](20),@longitutde)+' '+CONVERT([varchar](20),@latitude)+')', 4326);
    SELECT *, DistanceToUser = Location.STDistance(@userLocation) FROM [dbo].[Museums]
GO

С этим подходом я думаю, что решил проблему A.1, потому что вычисления должны быть оптимизированы. Полученная таблица теперь содержит столбец DistanceToUser, но, к сожалению, возникла новая проблема ...

Проблема B.1) : новая проблема заключается в том, что я не могу разыграть данные в модель. Я даже создал новую модель MuseumWithDistanceToUserModel : MuseumModel с атрибутом DistanceToUser, но такой подход привел к этой ошибке , а добавление атрибута [NotMapped] невозможно, поскольку в основном новая модель сопоставлена ​​с SP.

Если вы думаете, что это путь к go, я могу поделиться кодом и возникающими проблемами. Но действительно ли это хорошее решение?

Заключение

Я знаю, что мог бы делать операторы WHERE и ORDER BY в хранимой процедуре и создавать новую модель без какого-либо наследования. Но это кажется мне не совсем чистым. В дополнение к этому я все еще хочу иметь возможность манипулировать запросом на контроллере. Редактирование SP на БД каждый раз, когда необходима корректировка, не является предпочтительным. Пожалуйста, поделитесь своими мыслями по этому поводу. Я благодарен за все:)

1 Ответ

1 голос
/ 14 апреля 2020

Первая проблема заключается в том, что вы не знаете, что такое структура сущностей (EF) и хранимые процедуры (SP). Оба одинаковы на большой картине. EF конвертирует ваш код в запрос sql, выполняет его и возвращает результат.

SP, он хранится на sql сервере, когда вы его вызываете, он даст результаты. Затем вам нужно сопоставить эти результаты с моделью (c#).

Поэтому в конце он выполнит тот же запрос.

И, кроме обоих методов, вы можете выполнить запрос напрямую, используя EF.

public async Task<List<Museum>> GetMuseumsByRadius(Point point, double radius){
     return await _context.MuseumTemp
        .FromSql("SELECT Id, MuseumName, ST_Distance(Location::geography, ST_GeomFromText({0}, 4326)) as DistanceToUser  
                  FROM Museums 
                  WHERE ST_Distance(Location::geography, ST_GeomFromText({0}, 4326)) <= {1}"
                , point.AsText(), radius)
        .OrderByDescending(x => x.Distance)
        .AsNoTracking()
        .ToListAsync();
}

public class MuseumTemp
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string MuseumName { get; set; }

    public double? DistanceToUser { get; set; }

    [JsonIgnore]
    public Point Location { get; set; }
}

public virtual DbSet<MuseumTemp> MuseumTemps { get; set; }

// convert lnt,lat to a point
    Point userPoint = new Point(lng, lat)
    {
        SRID = 4326
    };

Альтернатива, это создаст оптимальный запрос по efcore. Вы можете получить сгенерированный запрос, используя выход веб-сервера (VS) или sql профилировщик сервера.

return await _context.Museums
       .Where(x => x.Location.IsWithinDistance(userLocation, distance))
       .OrderBy(x => x.Location.Distance(userLocation))
       .Select(e => new MuseumModel
                    {
                        Id  = e.Id,
                        MuseumName  = e.MuseumName 
                        DistanceToUser  = e.Location.Distance(userLocation)
                    })
       .ToListAsync();

public class MuseumModel
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string MuseumName { get; set; }

    [NotMapped]
    public double? DistanceToUser { get; set; }

    [JsonIgnore]
    public Geometry Location { get; set; }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...