Dapper.Query> Вернуть коллекцию нулей - PullRequest
1 голос
/ 06 ноября 2019

Простая проблема (РЕДАКТИРОВАНИЕ: сначала показать легко воспроизводимый пример, а затем подробный сценарий)

Имея следующие классы:

public class SalesMetrics {
   public decimal SalesDollars { get; set; }
   public decimal SalesUnits { get; set; }
}

public class ProductGroup {
   public string ProductId { get; set; }
   public string ProductName { get; set; }
}

с использованием следующего DapperЗапрос, мой результат равен [{Key = null, Value = null}]:

IEnumerable<KeyValuePair<ProductGroup, SalesMetrics>> result = sqlConnection
   .Query<KeyValuePair<ProductGroup, SalesMetrics>>(
       sql: @"SELECT 
                  1 As ProductId,
                  'Test' AS ProductName, 
                  1.00 As SalesDollars, 
                  1 As SalesUnits");

Мне интересно, может ли Dapper обрабатывать KeyValuePair как тип вывода, так: как должен быть запрос?


Полный сценарий (Зачем мне это нужно)

Я создаю функцию построителя Sales Query, которая может группировать мои результаты продаж по разным предикатам группировки и должна возвращать другой тип результата на основе этогоПредикат type.

Я использую пакет Dapper nuget для получения результата от SQL-Server. Я использую Dapper.Query<T>() метод расширения для IDbConnection

В принципе, независимо от группировкиТип Я хочу вернуть sum SalesDollars & SalesUnits. Для этой части вывода я создал следующий класс SalesMetrics

Я хочу, чтобы моя функция Sales Query принимала класс Group (ProductGroup или любой другой класс ...) как generic parameter named TGroup, функция должна возвращать коллекцию KeyValuePair<TGroup,SalesMetric>

источник данных

Вот схема моей таблицы продаж FlatSales

CREATE TABLE dbo.FlatSales (
   SalesDate DATE NOT NULL,
   ProductId INT NOT NULL,
   ProductName VARCHAR(100) NOT NULL,
   ProductCategoryId INT NOT NULL,
   ProductCategoryName VARCHAR(100) NOT NULL,
   CustomerGroupId INT NOT NULL,
   CustomerGroupName VARCHAR(100) NOT NULL,
   CustomerId INT NOT NULL,
   CustomerName VARCHAR(100) NOT NULL,
   SalesUnits INT NOT NULL,
   SalesDollars INT NOT NULL
)

Там, где у меня проблема

У меня есть следующая функция для запросов к БД.

public static IEnumerable<KeyValuePair<TGroup,SalesMetrics>> SalesTotalsCompute<TGroup>(System.Data.IDbConnection connection)
{
    string[] groupByColumnNames = typeof(TGroup)
        .GetProperties(bindingAttr: System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
        .Select(x => x.Name)
        .ToArray();
    string joinedGroupByColumnsNames = string.Join(",", groupByColumnNames);

    return connection.Query<KeyValuePair<TGroup, SalesMetrics>>(sql: $@"
        SELECT SUM(SalesDollars) AS SalesDollars,
            SUM(SalesUnits) AS SalesUnits,
            {joinedGroupByColumnsNames}
        FROM dbo.FlatSales
        GROUP BY {joinedGroupByColumnsNames}
    ");
}

Коллекция NULL

Код does not fail, но он возвращает список KeyValuePair, в котором ключ и значение имеют значение NULL.

Я пытался присвоить псевдонимы моим столбцам, например ProductName as [Key.ProductName], нотогда это ничего не меняет (также не сбоит) ...

Сгенерированные запросы Sql для ProductGroup выглядят следующим образом (оба возвращают пустой KeyValuePair):

        SELECT SUM(SalesDollars) AS SalesDollars,
            SUM(SalesUnits) AS SalesUnits,
            ProductId,ProductName
        FROM dbo.FlatSales
        GROUP BY ProductId,ProductName

OR
        SELECT SUM(SalesDollars) AS SalesDollars as [Value.SalesDollars],
            SUM(SalesUnits) AS SalesUnits as [Value.SalesUnits],
            ProductId As [Key.ProductId],ProductName As [Key.ProductName]
        FROM dbo.FlatSales
        GROUP BY ProductId,ProductName

Есть идеи?

1 Ответ

1 голос
/ 07 ноября 2019

Я сомневаюсь, что Dapper поддерживает такие сложные объекты, как этот, из коробки.

Возможно, вы можете воспользоваться мультикартирующей функцией Dapper :

public static IEnumerable<KeyValuePair<TGroup, SalesMetrics>> SalesTotalsCompute<TGroup>(System.Data.IDbConnection connection)
{
    string joinedGroupByColumnsNames = string.Join(",", GetCachedColumnNamesFor<TGroup>());

    return connection.Query<TGroup, SalesMetrics, KeyValuePair<TGroup, SalesMetrics>>(
        sql: $@"SELECT {joinedGroupByColumnsNames},
                       SUM(SalesDollars) AS SalesDollars,
                       SUM(SalesUnits) AS SalesUnits
                  FROM dbo.FlatSales
              GROUP BY {joinedGroupByColumnsNames}",
        map: (groupData, salesMetricsData) => new KeyValuePair<TGroup, SalesMetrics>(groupData, salesMetricsData),
        splitOn: "SalesDollars");
}

Замечания

  • Я переупорядочил столбцы, потому что splitOn нужно имя столбца, в котором разделены два объекта, в противном случае вам нужно будет передать первый элемент измассив joinedGroupByColumnsNames, который немного более случайный
  • Если вы работаете в .NET Standard, рассмотрите возможность возврата ValueTuple вместо KeyValuePair *
  • Не используйте отражение для каждого вызова, я предлагаю добавить метод GetCachedColumnNamesFor, который делает отражение только один раз, используя статический ConcurrentDictionary, вызывая метод ConcurrentDictionary.GetOrAdd.

Другой подход

Вы также можете позволить ProductGroup наследовать от SalesMetrics (или создать интерфейс ISalesMetrics и позволить ProductGroup реализовать этот интерфейс) и выполнитьQuery<ProductGroup>(...). Дополнительным преимуществом было бы то, что повторяющиеся поля в обеих моделях были бы заблокированы компилятором.

Получившийся метод будет выглядеть следующим образом:

public static IEnumerable<TSalesData> SalesTotalsCompute<TSalesData>(System.Data.IDbConnection connection)
    where TSalesData : ISalesMetric
{
    string joinedGroupByColumnsNames = string.Join(",", GetCachedNonSalesMetricColumnNamesFor<TSalesData>());

    return connection.Query<TSalesData>(sql: $@"
        SELECT SUM(SalesDollars) AS SalesDollars,
            SUM(SalesUnits) AS SalesUnits,
            {joinedGroupByColumnsNames}
        FROM dbo.FlatSales
        GROUP BY {joinedGroupByColumnsNames}
    ");
}

Здесь метод GetCachedNonSalesMetricColumnNamesFor отражаетсвойства из TSalesData, исключая свойства из интерфейса ISalesMetric, снова кэшируют результат.

...