如何通过LINQ将同一类的对象属性合并在一起

时间:2019-02-28 22:51:46

标签: c# entity-framework linq unit-of-work

说我有一个类,我想选择它的多个对象,但最后要创建一个统一的对象。这是因为需要合并对象的收集属性。

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成为货币对,则必须采用两种货币。货币对由两个PartialCurrencyPairs组成。我们之所以可以拥有许多欧元或许多美元,是因为它们来自不同的来源。

这是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())
                };

这是我要在单个对象上的最终结果:DetailedCurrencyResponse对象。

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 :(得分:0)

您概述的查询将尝试为您返回一个Currency对象,但是鉴于您正在寻找具有给定缩写的任何对象,如果多个货币对象共享一个缩写,则由于多次返回,SingleOrDefault可能会出错。

听起来您想定义一个结构来表示货币对。该结构不是货币实体,而是一种不同的数据表示形式。这些通常称为ViewModel或DTO。定义了要返回的内容后,可以使用.Select()从“货币”和适用的缩写中填充该数字。

例如,如果我创建一个CurrencySummaryDto,它将具有货币ID,缩写和包含所有适用对的字符串:

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结构中进行选择即可。此示例将数据组合成一个字符串,其中EF表达式不支持string.Join(),因此我们必须将数据放入对象中,以移交给Linq2Object进行最终映射。

编辑:好的,您的要求/示例对对的结构要复杂得多,但是您应该能够将其用于查询,而不是通过移动实体的选择来选择整个实体图值进入主查询...但是...

鉴于数据关系的复杂性,我建议使用此方法,因为这将被视为只读结果,因此将在数据库中构造一个View来展平这些平均值和总数,然后绑定一个简化的实体而不是尝试使用EF Linq进行管理。我相信可以使用linq来完成,但是要看它会很繁重,并且基于视图的摘要实体在保持该逻辑在数据库中执行的同时,会更加整洁。