LINQ - преобразовать выбранную сумму в группу по сумме - PullRequest
0 голосов
/ 06 августа 2020

Я пытаюсь преобразовать некоторые из моих SQL запросов в LINQ-запросы.

У меня есть следующий запрос SQL, который в основном получает точки JourneyStart, Journey End и объединяется со всеми промежуточными точками для расчета пройденного расстояния.

DECLARE @utcStartDate DateTime2 = '2017-02-01 00:00:00.0000000'
DECLARE @utcEndDate DateTime2 = '2017-02-02 23:59:59.9999999'
DECLARE @assetId INT = 4019

SELECT 
     AssetId
    ,[Event]
    ,StartDateTime
    ,EndDateTime
    ,JourneyDistance = ROUND(SUM(DistanceCoveredK), 3, 2)
    ,TotalJourneyTime = JourneyTime
FROM (
    SELECT 
         AssetId = ignOn.iAssetId
        ,Startlogid = ignOn.iVehicleMonitoringId
        ,StartDateTime = ignOn.dtUTCDateTime    
        ,Endlogid = ignOff.iVehicleMonitoringId
        ,EndDateTime = ignOff.dtUTCDateTime
        ,[Event] = ignOff.eEventCode
        ,DistanceCoveredK = p.sptGeoLocaitonPoint.STDistance(
                                LEAD(p.sptGeoLocaitonPoint) OVER(PARTITION BY ignOn.iAssetId, ignOn.dtUTCDateTime ORDER BY p.dtUTCDateTime)) * 0.001
        ,JourneyTime = DATEDIFF(SECOND, ignOn.dtUTCDateTime, ignOff.dtUTCDateTime)

    FROM VehicleMonitoringLog ignOn

    CROSS APPLY (
        SELECT top(1) iVehicleMonitoringId, eEventCode, dtUTCDateTime, sptGeoLocaitonPoint 
        FROM VehicleMonitoringLog WHERE 
        iAssetId = ignOn.iAssetId AND dtUTCDateTime > ignOn.dtUTCDateTime AND eEventCode = 2
        ORDER by dtUTCDateTime
    ) ignOff

    INNER JOIN VehicleMonitoringLog p ON p.iAssetId = ignOn.iAssetId AND p.dtUTCDateTime >= ignOn.dtUTCDateTime AND p.dtUTCDateTime <= ignOff.dtUTCDateTime
    
    WHERE 
        ignOn.dtUTCDateTime > @utcStartDate AND ignOn.dtUTCDateTime < @utcEndDate
        AND ignOn.iAssetId = @assetId
        AND ignOn.eEventCode = 1
) g

GROUP BY AssetId, [Event], StartDateTime, EndDateTime, JourneyTime

При преобразовании в запрос LINQ я получил следующее:

public static void Main()
{
    var VehicleMonitoringLogs = VehicleMonitoringLog.GetData();

    DateTime _startDate = new DateTime(2000, 02, 01);
    DateTime _endDate = new DateTime(2020, 02, 02).AddDays(1).AddTicks(-1);

    var assetIds = new int[] { 2 };
    // end of params


    var locationLogsWithDistance =
                // first get all location logs based on parameters criteria
                from locationLog in VehicleMonitoringLogs
                                    .Where(s =>
                                            assetIds.Contains(s.AssetId)
                                            && s.LogDateTime >= _startDate
                                            && s.LogDateTime <= _endDate
                                            )
                                    .OrderBy(t => t.LogDateTime)
                    // then for each location log in the above criteria get it's previous position - this is to calculate distance between current position and previous - because a vehicle does not move in a straight line so we need this.
                let prevPosition = VehicleMonitoringLogs
                           .Where(s => s.AssetId == locationLog.AssetId
                                           && s.LogDateTime >= _startDate
                                           && s.LogDateTime <= _endDate
                                           && s.LogDateTime < locationLog.LogDateTime
                                           && s.LogId != locationLog.LogId)
                                           .OrderByDescending(s => s.LogDateTime)
                                           .FirstOrDefault()

                orderby locationLog.LogDateTime

                select new
                {
                    AssetId = locationLog.AssetId,
                    VehicleMonitoringId = locationLog.LogId,
                    EventCode = locationLog.EventCode,
                    LogDateTimeUTC = locationLog.LogDateTime,
                    // calculate distance from previous point and convert from meters to KM
                    DistanceFromPreviousPoint = Math.Round((prevPosition != null ? locationLog.GeoLocationPoint.Distance(prevPosition.GeoLocationPoint) ?? 0 : default(double)) * .001, 2)
                };


    // now once we have a list of location logs with distance from previous record we can start calculating the logic for Journey's.

    var journeyLogs =
                      // get all journey start events (Ignition On or EventCode == 1)
                      from journeyStart in locationLogsWithDistance.Where(s => s.EventCode == 1)

                          // get the corresponding End Event (Ignition Off or EventCode == 2) - so the first Ignition Off after the existing Ignition On Event
                      from journeyEnd in locationLogsWithDistance.Where(s =>
                                                            s.AssetId == journeyStart.AssetId
                                                            && s.EventCode == 2
                                                            && s.LogDateTimeUTC >= journeyStart.LogDateTimeUTC)
                                                            .OrderBy(t => t.LogDateTimeUTC)
                                                            .Take(1)
                      select new
                      {
                          AssetId = journeyStart.AssetId,

                          JourneyStartId = journeyStart.VehicleMonitoringId,
                          JourneyStartUtc = journeyStart.LogDateTimeUTC,

                          JourneyEndId = journeyEnd.VehicleMonitoringId,
                          JourneyEndUtc = journeyEnd.LogDateTimeUTC,

                          // finally the Distance Travelled is SUM of all distances between each location point between Journey Start and Journey End Events
                          DistanceTravelled = locationLogsWithDistance
                                          .Where(s =>
                                              s.LogDateTimeUTC >= journeyStart.LogDateTimeUTC &&
                                              s.LogDateTimeUTC <= journeyEnd.LogDateTimeUTC &&
                                              s.AssetId == journeyStart.AssetId)
                                          .Sum(r => r.DistanceFromPreviousPoint)
                      };

    foreach (var j in journeyLogs)
    {

        Console.WriteLine("{0} | {1} | {2} | {3}", j.AssetId, j.DistanceTravelled, j.JourneyStartUtc, j.JourneyEndUtc);
    }

    Console.WriteLine("Complete.....");
    Console.ReadKey();

}

Результат правильный и ожидаемый, но SQL запрос выполняет новые SELECT, а затем SUM журналов местоположения. Поэтому я хотел бы преобразовать эту часть в оператор group by.

DistanceTravelled = locationLogsWithDistance
                                                .Where(s => 
                                                    s.LogDateTimeUTC >= journeyStart.LogDateTimeUTC && 
                                                    s.LogDateTimeUTC <= journeyEnd.LogDateTimeUTC && 
                                                    s.AssetId == journeyStart.AssetId)
                                                .Sum(r => r.DistanceFromPreviousPoint)

Лог c будет группироваться по journeyStart.AssetId, journeyStart.LogDateTimeUTC, а затем СУММИРОВАТЬ DistanceFromPreviousPoint. Но я не могу точно определить способ или синтаксис для этого.

ОБРАЗЕЦ ДАННЫХ И ЗАПРОС

DO TNET FIDDLE И C# ДАННЫЕ

Ответы [ 2 ]

1 голос
/ 07 августа 2020

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

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

double GetDistance(VehicleMonitoringLog log0, VehicleMonitoringLog log1) =>
    (log0.GeoLocationPoint.Distance(log1.GeoLocationPoint) ?? 0.0) * .001;

Это просто функция для вычисления расстояния.

Вот более сложный:

IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, Func<T, bool> open, Func<T, bool> close)
{
    IEnumerable<T> Output(IEnumerator<T> enumerator)
    {
        yield return enumerator.Current;
        while (enumerator.MoveNext())
        {
            yield return enumerator.Current;
            if (close(enumerator.Current))
            {
                yield break;
            }
        }
    }
    var e = source.GetEnumerator();
    while (e.MoveNext())
    {
        if (open(e.Current))
        {
            yield return Output(e).ToArray();
        }
    }
}

Это позволяет мне взять последовательность и разделите его на подпоследовательности, в которых есть начальное и конечное условие. В вашем случае EventCode == 1 и EventCode == 2.

Итак, учитывая это, вот мой последний запрос:

var journeyLogs =
    from s in VehicleMonitoringLogs
    where assetIds.Contains(s.AssetId)
    where s.LogDateTime >= _startDate
    where s.LogDateTime <= _endDate
    orderby s.LogDateTime
    group s by s.AssetId into gs
    from partition in Partition(gs, t => t.EventCode == 1, t => t.EventCode == 2)
    let first = partition.First()
    where first.EventCode == 1 // ensures we start with a 1
    let last = partition.Last()
    where last.EventCode == 2 // ensures we end with a 2
    select new
    {
        AssetId = first.AssetId,
        JourneyStartId = first.LogId,
        JourneyStartUtc = first.LogDateTime,
        JourneyEndId = last.LogId,
        JourneyEndUtc = last.LogDateTime,           
        DistanceTravelled = partition.Skip(1).Zip(partition, (p1, p0) => GetDistance(p0, p1)).Sum()
    };

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

0 голосов
/ 06 августа 2020

SQL - очень ограниченный язык программирования, а c# намного лучше. Никогда не пытайтесь использовать синтаксис SQL непосредственно в c#. Ищите способ получше. Я предположил, что каждое путешествие начинается с события 1 и заканчивается событием 2. См. Код ниже

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime  utcStartDate =  DateTime.Parse("2017-02-01 00:00:00.0000000");
            DateTime utcEndDate = utcStartDate.AddDays(2);
            int assetId = 4019;

            List<VehicleMonitoringLogs> logs = new List<VehicleMonitoringLogs>();

            List<VehicleMonitoringLogs> idDates = logs.Where(x => (x.iAssetId == assetId) && (x.dtUTCDateTime > utcStartDate) && (x.dtUTCDateTime < utcEndDate))
                .OrderByDescending(x => x.dtUTCDateTime)
                .ToList();

            var eventOns = idDates.Where(x => x.eEventCode == 2).Select((x,i) => new {endJourney = x, index = i}).ToList();
            var eventOffs = idDates.Where(x => x.eEventCode == 1).Select((x, i) => new { startJourney = x, index = i }).ToList();
            
            var results = (from _on in eventOns 
                          join off in eventOffs on _on.index equals off.index
                          select new { sptGeoLocaitonPoint = .001M * (_on.endJourney.sptGeoLocaitonPoint - off.startJourney.sptGeoLocaitonPoint), JourneyTime = _on.endJourney.dtUTCDateTime - off.startJourney.dtUTCDateTime}
                          ).ToList();
        }
    }
    public class VehicleMonitoringLogs
    {
        public int iAssetId { get; set; }
        public string iVehicleMonitoringId { get; set; }
        public DateTime dtUTCDateTime { get; set; }
        public int eEventCode { get; set; }
        public decimal sptGeoLocaitonPoint { get; set; }
    }
 
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...