Как объединить свойства объекта одного класса вместе через LINQ - PullRequest
0 голосов
/ 01 марта 2019

Скажем, у меня есть класс, я хочу выбрать несколько объектов из него, но в конце создаю один объединенный объект.Это из-за требования к свойствам коллекции объекта, который нужно объединить.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
using Nozomi.Base.Core;

namespace Nozomi.Data.Models.Currency
{
    public class Currency : BaseEntityModel
    {
        public Currency(ICollection<Currency> currencies)
        {
            if (currencies.Any())
            {
                var firstCurr = currencies.FirstOrDefault();

                if (firstCurr != null)
                {
                    // Doesn't matter...
                    Id = firstCurr.Id;
                    CurrencyTypeId = firstCurr.Id;
                    CurrencyType = firstCurr.CurrencyType;
                    Abbrv = firstCurr.Abbrv;
                    Name = firstCurr.Name;
                    CurrencySourceId = firstCurr.CurrencySourceId;
                    CurrencySource = firstCurr.CurrencySource;
                    WalletTypeId = firstCurr.WalletTypeId;
                    PartialCurrencyPairs = currencies
                        .SelectMany(c => c.PartialCurrencyPairs)
                        .DefaultIfEmpty()
                        .ToList();
                }
            }
        }

        [Key]
        public long Id { get; set; }

        public long CurrencyTypeId { get; set; }
        public CurrencyType CurrencyType { get; set; }

        public string Abbrv { get; set; } // USD? MYR? IND?

        public string Name { get; set; }

        public long CurrencySourceId { get; set; }
        public Source CurrencySource { get; set; }

        // This will have a number if it is a crypto pair to peg to proper entities
        public long WalletTypeId { get; set; } = 0;

        public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }

        public bool IsValid()
        {
            return !String.IsNullOrEmpty(Abbrv) && !String.IsNullOrEmpty(Name) && CurrencyTypeId > 0 && CurrencySourceId > 0;
        }
    }
}

Вот что такое PartialCurrencyPair:

namespace Nozomi.Data.Models.Currency
{
    /// <summary>
    /// Partial currency pair.
    /// </summary>
    public class PartialCurrencyPair
    {
        public long CurrencyId { get; set; }

        public long CurrencyPairId { get; set; }

        public bool IsMain { get; set; } = false;

        public CurrencyPair CurrencyPair { get; set; }
        public Currency Currency { get; set; }
    }
}

Так что, в принципе, если вы хотитечтобы сделать EURUSD, вам нужно взять две валюты, чтобы сформировать пару.CurrencyPair состоит из двух парциальных валютных пар.Причина, по которой у нас может быть много евро или много долларов, заключается в том, что они поступают из разных источников.

Вот что такое CurrencyPair:

public class CurrencyPair : BaseEntityModel
    {
        [Key]
        public long Id { get; set; }

        public CurrencyPairType CurrencyPairType { get; set; }

        /// <summary>
        /// Which CPC to rely on by default?
        /// </summary>
        public string DefaultComponent { get; set; }

        public long CurrencySourceId { get; set; }
        public Source CurrencySource { get; set; }

        // =========== RELATIONS ============ //
        public ICollection<CurrencyPairRequest> CurrencyPairRequests { get; set; }
        public ICollection<WebsocketRequest> WebsocketRequests { get; set; }
        public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }

        public bool IsValid()
        {
            var firstPair = PartialCurrencyPairs.First();
            var lastPair = PartialCurrencyPairs.Last();

            return (CurrencyPairType > 0) && (!string.IsNullOrEmpty(APIUrl)) 
                                          && (!string.IsNullOrEmpty(DefaultComponent))
                                          && (CurrencySourceId > 0)
                                          && (PartialCurrencyPairs.Count == 2)
                                          && (firstPair.CurrencyId != lastPair.CurrencyId)
                                          && (!firstPair.IsMain == lastPair.IsMain);
        }
    }

У меня есть IQueryable для объединения водна единая валюта.

Код с комментариями (В основном комментарии говорят о том, чего я пытаюсь достичь.

var query = _unitOfWork.GetRepository<Currency>()
                .GetQueryable()
                // Do not track the query
                .AsNoTracking()
                // Obtain the currency where the abbreviation equals up
                .Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
                            && c.DeletedAt == null && c.IsEnabled)
                // Something here that will join the PartialCurrencyPair collection together and create one single Currency object.
                .SingleOrDefault();

Как мне это сделать? Большое спасибовперёд! Вот прогресс , которого я добился до сих пор, и это работает, но я очень симпатичен LINQ имеет прекрасный способ сделать это лучше и оптимизированнее :

var combinedCurrency = new Currency(_unitOfWork.GetRepository<Currency>()
                .GetQueryable()
                // Do not track the query
                .AsNoTracking()
                // Obtain the currency where the abbreviation equals up
                .Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
                            && c.DeletedAt == null && c.IsEnabled)
                .Include(c => c.PartialCurrencyPairs)
                .ThenInclude(pcp => pcp.CurrencyPair)
                .ThenInclude(cp => cp.CurrencyPairRequests)
                .ThenInclude(cpr => cpr.RequestComponents)
                .ThenInclude(rc => rc.RequestComponentDatum)
                .ThenInclude(rcd => rcd.RcdHistoricItems)
                .ToList());


return new DetailedCurrencyResponse
                {
                    Name = combinedCurrency.Name,
                    Abbreviation = combinedCurrency.Abbrv,
                    LastUpdated = combinedCurrency.PartialCurrencyPairs
                        .Select(pcp => pcp.CurrencyPair)
                        .SelectMany(cp => cp.CurrencyPairRequests)
                        .SelectMany(cpr => cpr.RequestComponents)
                        .OrderByDescending(rc => rc.ModifiedAt)
                        .FirstOrDefault()?
                        .ModifiedAt ?? DateTime.MinValue,
                    WeeklyAvgPrice = combinedCurrency.PartialCurrencyPairs
                        .Select(pcp => pcp.CurrencyPair)
                        .Where(cp => cp.CurrencyPairRequests
                            .Any(cpr => cpr.DeletedAt == null && cpr.IsEnabled))
                        .SelectMany(cp => cp.CurrencyPairRequests)
                        .Where(cpr => cpr.RequestComponents
                            .Any(rc => rc.DeletedAt == null && rc.IsEnabled))
                        .SelectMany(cpr => cpr.RequestComponents
                            .Where(rc =>
                                rc.ComponentType.Equals(ComponentType.Ask) ||
                                rc.ComponentType.Equals(ComponentType.Bid)))
                        .Select(rc => rc.RequestComponentDatum)
                        .SelectMany(rcd => rcd.RcdHistoricItems
                            .Where(rcdhi => rcdhi.CreatedAt >
                                            DateTime.UtcNow.Subtract(TimeSpan.FromDays(7))))
                        .Select(rcdhi => decimal.Parse(rcdhi.Value))
                        .DefaultIfEmpty()
                        .Average(),
                    DailyVolume = combinedCurrency.PartialCurrencyPairs
                        .Select(pcp => pcp.CurrencyPair)
                        .Where(cp => cp.CurrencyPairRequests
                            .Any(cpr => cpr.DeletedAt == null && cpr.IsEnabled))
                        .SelectMany(cp => cp.CurrencyPairRequests)
                        .Where(cpr => cpr.RequestComponents
                            .Any(rc => rc.DeletedAt == null && rc.IsEnabled))
                        .SelectMany(cpr => cpr.RequestComponents
                            .Where(rc => rc.ComponentType.Equals(ComponentType.VOLUME)
                                         && rc.DeletedAt == null && rc.IsEnabled))
                        .Select(rc => rc.RequestComponentDatum)
                        .SelectMany(rcd => rcd.RcdHistoricItems
                            .Where(rcdhi => rcdhi.CreatedAt >
                                            DateTime.UtcNow.Subtract(TimeSpan.FromHours(24))))
                        .Select(rcdhi => decimal.Parse(rcdhi.Value))
                        .DefaultIfEmpty()
                        .Sum(),
                    Historical = combinedCurrency.PartialCurrencyPairs
                        .Select(pcp => pcp.CurrencyPair)
                        .SelectMany(cp => cp.CurrencyPairRequests)
                        .SelectMany(cpr => cpr.RequestComponents)
                        .Where(rc => componentTypes != null 
                                     && componentTypes.Any()
                                     && componentTypes.Contains(rc.ComponentType)
                                     && rc.RequestComponentDatum != null
                                     && rc.RequestComponentDatum.IsEnabled 
                                     && rc.RequestComponentDatum.DeletedAt == null
                                     && rc.RequestComponentDatum.RcdHistoricItems
                                         .Any(rcdhi => rcdhi.DeletedAt == null &&
                                                       rcdhi.IsEnabled))
                        .ToDictionary(rc => rc.ComponentType,
                            rc => rc.RequestComponentDatum
                                .RcdHistoricItems
                                .Select(rcdhi => new ComponentHistoricalDatum
                                {
                                    CreatedAt = rcdhi.CreatedAt,
                                    Value = rcdhi.Value
                                })
                                .ToList())
                };

Вот конечный результат, который я хочу получить для этого единственного объекта: объект DetailCurrencyResponse.

public class DistinctiveCurrencyResponse
    {
        public string Name { get; set; }

        public string Abbreviation { get; set; }

        public DateTime LastUpdated { get; set; }

        public decimal WeeklyAvgPrice { get; set; }

        public decimal DailyVolume { get; set; }
    }

Исторический элемент данных - это, в основном, kvp, где Key (ComponentType) - это перечисление.

public class DetailedCurrencyResponse : DistinctiveCurrencyResponse
    {
        public Dictionary<ComponentType, List<ComponentHistoricalDatum>> Historical { get; set; }
    }

public class ComponentHistoricalDatum
    {
        public DateTime CreatedAt { get; set; }

        public string Value { get; set; }
    }

1 Ответ

0 голосов
/ 01 марта 2019

Изложенный вами запрос попытается вернуть вам один объект Currency, но, если вы ищете любой объект с данной аббревиатурой, если несколько объектов валюты имеют общее сокращение, SingleOrDefault может привести к ошибке из-за множественных возвратов.

Похоже, вы хотите определить структуру для представления валютных пар.Эта структура является не сущностью Валюта, а другим представлением данных.Они обычно называются ViewModels или DTO.После того, как вы определили, что вы хотите вернуть, вы можете использовать .Select(), чтобы заполнить это из валюты и применимых сокращений.

Например, если я создам CurrencySummaryDto, который будет иметь идентификатор валюты, сокращение,и строка, содержащая все применимые пары:

public class CurrencySummaryDto
{
    public long CurrencyId { get; set; }
    public string Abbreviation { get; set; }
    public string Pairs { get; set;} 
}

... затем запрос ...

var currencySummary = _unitOfWork.GetRepository<Currency>()
    .GetQueryable()
    .AsNoTracking()
    .Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
        && c.DeletedAt == null && c.IsEnabled)
    .Select( c => new {
        c.Id,
        c.Abbrv,
        Pairs = c.PartialCurrencyPairs.Select(pc => pc.PairName).ToList() // Get names of pairs, or select another annonymous type for multiple properties you care about...
    }).ToList() // Alternatively, when intending for returning lots of data use Skip/Take for paginating or limiting resulting data.    
    .Select( c => new CurrencySummaryDto
    {
        CurrencyId = c.Id,
        Abbreviation = c.Abbrv,
        Pairs = string.Join(", ", c.Pairs)
    }).SingleOrDefault();

Это если вы хотите сделать что-то вроде объединения данных извалютные пары в нечто вроде строки.Если вы готовы оставить их как набор упрощенных данных, дополнительный анонимный тип и .ToList() не требуются, просто выберите их непосредственно в структуре Dto.В этом примере данные объединяются в строку, в которой string.Join() не поддерживается в выражениях EF, поэтому мы должны передать наши данные в объекты для передачи в Linq2Object для окончательного отображения.

Редактировать: Хорошо, вы 'Требование / пример стали намного сложнее с парной структурой, но вы должны иметь возможность использовать это в запросе, а не выделять весь граф сущностей, перемещая выбор этих значений в основной запрос ...Однако ...

Учитывая сложность отношений данных, подход, который я бы рекомендовал использовать, поскольку предполагается, что это результат только для чтения, заключался бы в создании представления в базе данных для выравнивания этих средних значений.и итоги, а затем связать упрощенную сущность с этим представлением, а не пытаться управлять этим с помощью EF Linq.Я полагаю, что это можно сделать с помощью linq, но это будет довольно обременительно, а сводная сущность на основе представления будет намного чище, сохраняя выполнение этой логики в базе данных.

...