【问题标题】:How to merge object properties of the same class together via LINQ如何通过 LINQ 将同一类的对象属性合并在一起
【发布时间】:2019-02-28 22:51:46
【问题描述】:

假设我有一个类,我想选择它的多个对象,但最终创建一个统一的对象。这是因为需要组合对象的集合属性。

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 由两个 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 可以组合成一种货币。

使用 cmets 编写代码(cmets 基本上会告诉您我想要实现的目标。

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; }
    }

【问题讨论】:

  • 使用 string.Join("^", string[])。选择像 ^ 这样的字符,它不属于您尝试组合的字符串中的任何字符。
  • @jdweng 当我的集合类型是对象时,这怎么可能
  • @Nicholas Something here that will join the PartialCurrencyPair collection together and create one single Currency object. - 单一货币对象的类型是什么?逗号分隔的字符串?另外不要忘记在问题中包含您的PartialCurrencyPair 模型。
  • 当对象比较复杂的时候,需要一个方法来提取对象的属性,使用ToString()这样所有类型都是一样的。
  • @TanvirArjel 完成!谢谢!单个对象是DetailedCurrencyResponse 对象。它现在附在问题上。

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


【解决方案1】:

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

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

例如,如果我创建一个 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 进行最终映射。

编辑:好的,您的要求/示例对配对结构变得更加复杂,但是您应该能够将其利用到查询中,而不是通过移动这些实体的选择来选择整个实体图将值添加到主查询中...但是...

鉴于数据关系的复杂性,我建议使用的方法是,因为这将被假定为只读结果,因此在数据库中构造一个视图以展平这些平均值和总计,然后绑定一个简化的实体到此视图,而不是尝试使用 EF Linq 管理此视图。我相信可以用 linq 来完成,但是看起来会很麻烦,而且基于视图的汇总实体会干净很多,同时保持这个逻辑在数据库中执行。

【讨论】:

  • 你好!你真的超级亲密!只是关于 PartialCurrencyPairs 是如何工作的,我没有给你足够的信息来理解为什么它是这样的结构。我已经用更多细节更新了我的问题,我相信这些细节足以让您了解我如何才能获得我正在寻找的答案!
  • 编辑了我的回复。我认为如果你真的想要,你可以利用 EF Linq,但是对于这样的摘要结果,我建议使用 DB 视图来展平这些结果,然后在视图中绑定一个简单的实体。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-02
  • 1970-01-01
  • 1970-01-01
  • 2016-08-29
  • 2020-11-16
相关资源
最近更新 更多