在C#中两个CSV文件上的复合联接

问题描述 投票:0回答:2

从每日水果价格表开始

fruits.csv

Day,Name,Kind,Price
2019-09-04,"apple","red",63.09
2019-09-04,"apple","yellow",52.14
2019-09-04,"orange","navel",41.18
2019-09-04,"orange","blood",41.18
2019-09-03,"apple","red",63.07
2019-09-03,"apple","yellow",52.11
2019-09-03,"orange","navel",41.13
2019-09-03,"orange","blood",41.13

我想按名称种类插入参考价格

fruit_ref_prices.csv

Name,Kind,Reference_Price
"apple","red",60.00
"apple","yellow",50.00
"orange","navel",40.00
"orange","blood",42.00

得出下表

Day,Name,Kind,Price,Reference_Price
2019-09-04,"apple","red",63.09,60.00
2019-09-04,"apple","yellow",52.14,50.00
2019-09-04,"orange","navel",41.18,40.00
2019-09-04,"orange","blood",41.18,42.00
2019-09-03,"apple","red",63.07,60.00
2019-09-03,"apple","yellow",52.11,50.00
2019-09-03,"orange","navel",41.13,40.00
2019-09-03,"orange","blood",41.13,42.00

使用C#内置的类似SQL的语法,该解决方案应该很简单,我相信答案就在以下教程页面之一中:

但是我很难识别这种语言的语法。

我在下面尝试而不是写

join fruit_ref in fruit_refs on fruit.name equals fruit_ref.name

我应该会写

join fruit_ref in fruit_refs on fruit.name equals fruit_ref.name
and fruit.kind equals fruit_ref.kind

但是不接受布尔表达式。为什么?

我的尝试是:

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

namespace MyConsoleApplication
{
    class Program
    {
        const string root = @"c:\path\to\here\";

        const string file1_in = root + @"fruits.csv";
        const string file2_in = root + @"fruit_ref_prices.csv";

        static void Main(string[] args)
        {
            Fruit_Basket fruit_basket = new Fruit_Basket(file1_in, file2_in);
            fruit_basket.PrintFruits();
        }
    }
    public class Fruit
    {
        public DateTime day { get; set; }
        public string name { get; set; }
        public string kind { get; set; }
        public decimal price { get; set; }

        public Fruit(DateTime newFruit_day,
                     string newFruit_name,
                     string newFruit_kind,
                     decimal newFruit_price)

        {
            this.day   = newFruit_day;
            this.name  = newFruit_name;
            this.kind  = newFruit_kind;
            this.price = newFruit_price;
        }
    }
    public class Fruit_Ref
    {
        public string name;
        public string kind;
        public decimal reference_price;
        public Fruit_Ref(string newName, string newKind, decimal newRef_Price)
        {
            this.name = newName;
            this.kind = newKind;
            this.reference_price = newRef_Price;
        }
    }
    public class Fruit_Basket {
        public List<Fruit> fruits { get; set; }
        public List<Fruit_Ref> fruit_refs { get; set; }
        public Fruit_Basket(string file1_in, string file2_in) {
            build_fruit_list(file1_in);
            build_fruit_ref_list(file2_in);
        }

        public void build_fruit_list(string file_in)
        {
            fruits = new List<Fruit>();

            int count = 0;
            StreamReader reader = new StreamReader(file_in);
            string line = "";

            while ((line = reader.ReadLine()) != null)
            {
                if (++count > 1)
                {
                    string[] splitLine = line.Split(new char[] { ',' }).ToArray();

                    var newFruit_day = DateTime.Parse(splitLine[0]);
                    var newFruit_name = splitLine[1];
                    var newFruit_kind = splitLine[2];
                    var newFruit_price  = decimal.Parse(splitLine[3]);

                    Fruit newFruit = new Fruit(newFruit_day,
                                               newFruit_name,
                                               newFruit_kind,
                                               newFruit_price);

                    fruits.Add(newFruit);
                }
            }
            reader.Close();
        }

        public void build_fruit_ref_list(string file_in)
        {
            fruit_refs = new List<Fruit_Ref>();

            int count = 0;
            StreamReader reader = new StreamReader(file_in);
            string line = "";

            while ((line = reader.ReadLine()) != null)
            {
                if (++count > 1)
                {
                    string[] splitLine = line.Split(new char[] { ',' }).ToArray();

                    var newFruit_name = splitLine[0];
                    var newFruit_kind = splitLine[1];
                    var newFruit_ref_price  = decimal.Parse(splitLine[2]);

                    Fruit_Ref newFruit_ref = new Fruit_Ref(newFruit_name,
                                                           newFruit_kind,
                                                           newFruit_ref_price);

                    fruit_refs.Add(newFruit_ref);
                }
            }
            reader.Close();
        }

        public void PrintFruits()
        {
            var innerJoinQuery =
                from fruit in fruits
                join fruit_ref in fruit_refs on fruit.name equals fruit_ref.name
                select new { Day = fruit.day, Name = fruit.name, Kind = fruit.kind,
                Price = fruit.price, Reference_Price = fruit_ref.reference_price };

            Console.WriteLine($@"""Date"",""Name"",""Kind"",""Price"",""Ref Price""");

            foreach (var i in innerJoinQuery)
            {
                Console.WriteLine($@"{i.Day},{i.Kind},{i.Price},{i.Reference_Price}");
            }
        }
    }
}
c#
2个回答
0
投票

[请将equals子句更改为on new { fruit.name, fruit.kind } equals new { fruit_ref.name, fruit_ref.kind }

为什么需要这个

查询具有两种匿名类型(一种用于左表,一种用于右表)。因此,要比较那些匿名类型和比较它们,该语句应使用new关键字

查询:

var innerJoinQuery = from fruit in fruits
                join fruit_ref in fruit_refs on new { fruit.name, fruit.kind } equals new { fruit_ref.name, fruit_ref.kind }
                select new { Day = fruit.day, Name = fruit.name, Kind = fruit.kind,
                Price = fruit.price, Reference_Price = fruit_ref.reference_price };

0
投票

您还可以重构代码以使用CsvHelper NuGet包读取/写入CSV文件。

首先,您可以使这些类反映水果,水果参考和最终的水果结构。

public class Fruit
{
    public string Day { get; set; }
    public string Name { get; set; }
    public string Kind { get; set; }
    public string Price { get; set; }
}

public class FruitReferencePrice
{
    public string Name { get; set; }
    public string Kind { get; set; }
    public string Reference_Price { get; set; }
}

public class FruitFinal
{
    public string Day { get; set; }
    public string Name { get; set; }
    public string Kind { get; set; }
    public string Price { get; set; }
    public string ReferencePrice { get; set; }
    public override string ToString()
    {
        return $"Day={Day},Name={Name},Kind={Kind},Price={Price},Reference_Price={ReferencePrice}";
    }
}

然后,您可以使用两种方法将每个CSV文件的行返回到List<Fruit>和`List。

private static IEnumerable<Fruit> BuildFruitList(string csvFilePath)
{
    if (!File.Exists(csvFilePath))
    {
        throw new FileNotFoundException("Could not locate CSV at path " + csvFilePath, csvFilePath);
    }

    try
    {
        using var fileReader = File.OpenText(csvFilePath);
        using var csv = new CsvReader(fileReader);
        return csv.GetRecords<Fruit>().ToList();
    } catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return Enumerable.Empty<Fruit>().ToList();
    }
}

private static IEnumerable<FruitReferencePrice> BuildFruitReferenceList(string csvFilePath)
{
    if (!File.Exists(csvFilePath))
    {
        throw new FileNotFoundException("Could not locate CSV at path " + csvFilePath, csvFilePath);
    }

    try
    {
        using var fileReader = File.OpenText(csvFilePath);
        using var csv = new CsvReader(fileReader);
        return csv.GetRecords<FruitReferencePrice>().ToList();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return Enumerable.Empty<FruitReferencePrice>().ToList();
    }
}

然后您可以执行分组联接并输出合并的结果。

var path1 = "PATH\\fruits.csv";
var path2 = "PATH\\fruit_ref_prices.csv";

var fruitList = BuildFruitList(path1);
var fruitReferencePrices = BuildFruitReferenceList(path2);

var groupedJoin = from fruit in fruitList
                  join fruit_ref in fruitReferencePrices 
                  on new { fruit.Name, fruit.Kind } equals new { fruit_ref.Name, fruit_ref.Kind }
                  select new FruitFinal
                  {
                      Day = fruit.Day,
                      Name = fruit.Name,
                      Kind = fruit.Kind,
                      Price = fruit.Price,
                      ReferencePrice = fruit_ref.Reference_Price
                  };

foreach (var fruit in groupedJoin)
{
    Console.WriteLine(fruit.ToString());
}

合并结果:

Day=2019-09-04,Name=apple,Kind=red,Price=63.09,Reference_Price=60.00
Day=2019-09-04,Name=apple,Kind=yellow,Price=52.14,Reference_Price=50.00
Day=2019-09-04,Name=orange,Kind=navel,Price=41.18,Reference_Price=40.00
Day=2019-09-04,Name=orange,Kind=blood,Price=41.18,Reference_Price=42.00
Day=2019-09-03,Name=apple,Kind=red,Price=63.07,Reference_Price=60.00
Day=2019-09-03,Name=apple,Kind=yellow,Price=52.11,Reference_Price=50.00
Day=2019-09-03,Name=orange,Kind=navel,Price=41.13,Reference_Price=40.00
Day=2019-09-03,Name=orange,Kind=blood,Price=41.13,Reference_Price=42.00
© www.soinside.com 2019 - 2024. All rights reserved.