Оптимизировать основной запрос EF в алфавитном порядке - PullRequest
0 голосов
/ 07 июня 2019

В последнее время я имел дело с проблемой, и хотя у меня есть некоторые решения, я бы хотел найти лучшее с любой точки зрения.

Допустим, у меня есть приложение WPF с EF Core. В моей базе данных около 3000 клиентов (в моем случае SQLite, но в будущем это должно работать и с более медленными). Когда пользователь открывает список клиентов, я загружаю только некоторые из них (количество = 50, страница = 0) в алфавитном порядке. Как только пользователь прокручивает страницу вниз, загружается еще 50 (количество = 50, страница = 1).

CustomerRepository.GetQueryableAll().Skip(page * quantity).Take(quantity).ToList();

Все отлично работает. Здесь возникает проблема: есть кнопка для создания нового клиента, которая открывает модальное окно. Допустим, пользователь создает клиента с начальной буквой W. Как только он / она нажимает SAVE, новый клиент сохраняется в базе данных, окно закрывается, и список необходимо перезагрузить. Но загрузка всего списка до W, конечно, очень медленная.

До сих пор я пытался выполнить запрос к базе данных в фоновом режиме и сохранить количество клиентов, начинающих с каждой буквы базы данных, в статическом словаре: как только удастся сохранить SAVE, я смогу угадать, сколько или больше «страниц» пропустить () в базе данных и получить группу из 50, в которой будет новый клиент. Это работает, это довольно быстро, но я боюсь, что это не будет работать в странах с нелатинскими алфавитами:

public async Task<Dictionary<char, int>> GetCustomersByInitialsCount()
{
    return await Task.Run(async delegate
    {
        var dictionary = new Dictionary<char, int>();
        for (char c = 'A'; c <= 'Z'; c++)
        {
            var count = await CustomerRepository.GetCustomerCountStartingWith(c.ToString());
            dictionary.Add(c, count);
        }
        return dictionary;
    });
}

[... and in the repository:]

public async Task<int> GetCustomerCountStartingWith(string startingLetter)
{
    using (var dbContext = new MyDbContext())
    {
        return await dbContext.Set<Customer>().CountAsync(p => p.LastName.ToUpper().StartsWith(startingLetter.ToUpper()));
    }
}

В противном случае вместо этого фонового запроса я мог бы также попытаться «угадать» нужную страницу в зависимости от начального символа, но я все еще озадачен неожиданными результатами, которые я мог получить с нелатинскими языками.

Если кто-нибудь знает лучшие инструменты или есть другие полезные идеи, я с удовольствием их рассмотрю!

Большое спасибо заранее и счастливого кодирования.

Ответы [ 2 ]

1 голос
/ 10 июня 2019

Альтернативное решение . С моей точки зрения, немного более эффективен

Ваша проблема сводится к тому, как получить номер строки нового клиента во всем наборе данных, упорядоченный по имени клиента.

First ofвсе, просто SQL для SQLite или MSSQL, вы можете решить проблему получения правильного номера страницы с помощью функции ROW_NUMBER .Пример запроса:

SELECT TOP 1 rnd.rownum, rnd.LastName
  from (SELECT  ROW_NUMBER() OVER( ORDER BY c.LastName) AS rownum, c.LastName
  FROM [Customer] c) rnd
WHERE rnd.LastName = '<your new customers name here>'

Итак, после получения точного значения округлого числа и уже имеющегося параметра количества страниц, вы можете легко рассчитать нужную страницу.

Возвращаясь к вашему коду.Эта функция может быть реализована в EF с перегруженной версией метода Select, но, к сожалению, она еще не была реализована в EF Core для IQueryable ( см. ).Но вы все равно можете передать точный запрос прямо в БД, используя метод FromSql .

Решение состоит из двух шагов:

  1. Чтобы получить необходимые данные, вам нужночтобы определить Query для построителя модели (дополнительные поля, например, вам понадобятся только RowNum):

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Query<CustomerRownNum>();
    }
    
    public class CustomerRownNum
    {
        public long RowNum { get; set; }
        public Guid Id { get; set; }
        public string LastName { get; set; }
    }
    
  2. Затем вам нужно передать вышеупомянутый SQL-запрос вМетод запроса контекста следующим образом:

        string customerLastName = "<your customer's last name>";
        var result = dbContext.Query<CustomerRownNum>().FromSql(
            @"select top 1 rnd.RowNum, rnd.Id, rnd.LastName
                from 
                (SELECT  ROW_NUMBER() OVER( ORDER BY c.LastName) AS RowNum
                    , c.Id, c.LastName
                  FROM [Customer] c) rnd
                WHERE rnd.LastName = {0}", customerLastName).FirstOrDefault();
    

Наконец вы получите нужные данные прямо в переменной result.

Надеюсь, это поможет!

1 голос
/ 07 июня 2019

Что если вы добавите запрос, чтобы получить все первые «буквы» в вашей таблице?

public async Task<List<string>> GetCustomerFirstLetter()
{
    using (var dbContext = new MyDbContext())
    {
        return await dbContext.Set<Customer>().Select(x => x.lastName.Substring(0, 1)).Distinct().ToList();
    }
}

, а затем

public async Task<Dictionary<char, int>> GetCustomersByInitialsCount()
{
    return await Task.Run(async delegate
    {
        var dictionary = new Dictionary<char, int>();
        var letters = GetCustomerFirstLetter();
        foreach(letter in letters)
        {
            var count = await CustomerRepository.GetCustomerCountStartingWith(letter);
            dictionary.Add(letter, count);
        }
        return dictionary;
    });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...