質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.62%

LINQ to DatasetによるData Tableからのデータ抽出

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,699

sicnweouif

score 12

 前提・実現したいこと

C#でユーザの入力をもとにData Tableを検索し、抽出データを表示するシステムの実装を行っています。Data TableにはXMLファイルのデータを読み込んでいます。そこで、LINQ to DatasetによるData Tableからのデータ抽出を行おうと考えてます。

DataTableにはDateTime型の"日時"列があります.その"日時"列に注目して,ある日付の行データ,あるいはそこから遡った1週間分の行データを抽出したいと考えております.具体的に言えば,「2018/10/25から1週間前までのデータを抽出」といったような感じです.また検索条件の日付は,変数として可変に扱うことができるようにしたいです.
DateTime型の比較により,一定幅の連続した行データを抽出するにはwhere句でどのように検索条件を指定すればよいのでしょうか.

稚拙な質問ですが,どなたかお教え頂ければ幸いです。

 XMLファイル

<?xml version="1.0" encoding="utf-8"?>
<DocumentElement>
  <data>
    <日時>2018/10/23 9:50</日時>
    <場所>会議室</場所>
    <メモ>ミーティング</メモ>
  </data>
  <data>
    <日時>2018/09/23 18:30</日時>
    <場所>居酒屋</場所>
    <メモ>飲み会</メモ>
  </data>
  <data>
    <日時>2018/09/22 11:50</日時>
    <場所>温泉</場所>
    <メモ>旅行</メモ>
  </data>
  <data>
    <日時>2018/09/21 20:00</日時>
    <場所>居酒屋</場所>
    <メモ>飲み会</メモ>
  </data> 
</DocumentElement>

 該当のソースコード

namespace Program
{
    public partial class Display : Form
    {
        private string DataFileName = System.IO.Path.Combine(Application.StartupPath, "XMLFile1.xml");
        private DataSet Ds = new DataSet();
        private DataTable Dl = new DataTable();


        public SearchDisplay3()
        {
            InitializeComponent();
            Dl.Columns.Add("日時", typeof(DateTime));
        }

        private void Button_Click(object sender, EventArgs e)
        {
            Ds.Tables.Clear();
            Ds.ReadXml(DataFileName);
            Dl = Ds.Tables[0];          
        }
    }
}

 補足情報

修正が遅れてしまい,申し訳ございません.
少し強引なやり方かもしれませんが,XMLファイルを読み込む前に,DlにあらかじめDateTime型の列を追加しています.
XMLファイルの"日時"列の要素を2018年10月23日 15:00などにかえるとDlに読み込む際に
「文字列は有効なDate Timeではありませんでした.」
というエラーが出るので,DateTime型のデータしか受け付けない仕様になっているはずだと解釈しておりました.

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • Zuishin

    2018/10/26 13:41

    必要なのは私にとってではなく、回答者が回答するにあたって必要ということです。

    キャンセル

  • Zuishin

    2018/10/26 13:43

    またそれは、あなたが自分で解決できない以上、問題解決に必要ということです。

    キャンセル

  • Zuishin

    2018/10/27 11:17

    わかったら返事。わからなかったらお気の毒様。

    キャンセル

回答 3

checkベストアンサー

+4

少し強引なやり方かもしれませんが,XMLファイルを読み込む前に,DlにあらかじめDateTime型の列を追加しています.

それは意味がないです。xml ファイルにスキーマを付与して型を指定してから Ds.ReadXml(DataFileName); で読むようにしてください。

Data Tableを捨てているというのはどういうことでしょうか.

private DataTable Dl = new DataTable(); の Dl と Dl = Ds.Tables[0]; の Dl が指すオブジェクトは別物ですよ。

xml ファイルにスキーマがない場合、Ds.ReadXml(DataFileName); で xml ファイルを読んで DataSet/DataTable を作ると、DataTable の全部の列が String 型になるはずです。

スキーマというのがどういうものかと言うと、例えば以下の xml ファイルで <xs:schema ...>...</xs:schema> の部分がそれです。

<?xml version="1.0" standalone="yes"?>
<NewDataSet>
  <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="Table1">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="ID" type="xs:int" minOccurs="0" />
                <xs:element name="Name" type="xs:string" minOccurs="0" />
                <xs:element name="Price" type="xs:decimal" minOccurs="0" />
                <xs:element name="Discontinued" type="xs:boolean" minOccurs="0" />
                <xs:element name="Date" type="xs:dateTime" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <Table1>
    <ID>0</ID>
    <Name>Product Name_0</Name>
    <Price>123000</Price>
    <Discontinued>true</Discontinued>
    <Date>2018-10-26T10:18:11.9032337+09:00</Date>
  </Table1>
  <Table1>
    <ID>1</ID>
    <Name>Product Name_1</Name>
    <Price>246000</Price>
    <Discontinued>false</Discontinued>
    <Date>2018-10-27T10:18:11.9042409+09:00</Date>
  </Table1>

 ・・・中略・・・

</NewDataSet>

kiichi54321 さん、runny_nose さんの回答は DataTable の当該列が DateTime 型であるという前提だと思いますが、そうだとすると DataTable を直さないと話が違ってくると思います。

【追記】

下の私の 2018/10/27 11:23 のコメントで「後で検証して回答欄に追記しておきます」と書きましたが、それを以下に書きます。

上にアップしたスキーマ付きの xml ファイルを DataSet.ReadXml メソッドで DataSet/ DataTable に読み込めば Date 列は DateTime 型になります。

それから前回のスレッド https://teratail.com/questions/153141 の応用で DataRow[] 型のオブジェクトを取得できます。

具体的には、以下のようにすれば、

DataSet dataset1 = new DataSet();
dataset1.ReadXml("xml ファイルのパス");

DataRow[] datarows = (from row in dataset1.Tables[0].AsEnumerable()
                      let date = row.Field<DateTime>("Date")
                      where date <= DateTime.Now && date >= DateTime.Now.AddDays(-7)
                      select row).ToArray();

foreach (DataRow row in datarows)
{
    Console.WriteLine($"ID: {row[0]}, Name: {row[1]}, Price: {row[2]}, Discontinued: {row[3]}, Date: {row[4]}");
}

結果は以下のようになります:

ID: 9, Name: Product Name_9, Price: 1230000, Discontinued: False, Date: 2018/10/21 11:10:33
ID: 10, Name: Product Name_10, Price: 1353000, Discontinued: True, Date: 2018/10/22 11:10:33
ID: 11, Name: Product Name_11, Price: 1476000, Discontinued: False, Date: 2018/10/23 11:10:33
ID: 12, Name: Product Name_12, Price: 1599000, Discontinued: True, Date: 2018/10/24 11:10:33
ID: 13, Name: Product Name_13, Price: 1722000, Discontinued: False, Date: 2018/10/25 11:10:33
ID: 14, Name: Product Name_14, Price: 1845000, Discontinued: True, Date: 2018/10/26 11:10:33

【追記2】

下の私の 2018/10/27 15:22 のコメントで「具体的にどのようにするかは後で回答欄に追記しておきます」と書きましたが、それを以下に書きます。

コードを読めばわかると思うのでコードだけ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string dir = @"どこかの適当なフォルダ";

            DataSet ds = CreateDataSource();
            string file = "withschema.xml";
            ds.WriteXml(dir + file, XmlWriteMode.WriteSchema);

            file = "WithoutSchema.xml";
            ds.WriteXml(dir + file, XmlWriteMode.IgnoreSchema);

            DataSet dataset1 = new DataSet();
            DataSet dataset2 = new DataSet();

            dataset1.ReadXml(dir + "WithSchema.xml");
            dataset2.ReadXml(dir + "WithoutSchema.xml");

            Type type1 = dataset1.Tables[0].Columns["Date"].DataType;
            Type type2 = dataset2.Tables[0].Columns["Date"].DataType;

            Console.WriteLine($"Type: {type1}");
            Console.WriteLine($"Type: {type2}");

            // 結果は:
            // Type: System.DateTime
            // Type: System.String

            DataRow[] datarows = (from row in dataset1.Tables[0].AsEnumerable()
                                  let date = row.Field<DateTime>("Date")
                                  where date <= DateTime.Now && date >= DateTime.Now.AddDays(-7)
                                  select row).ToArray();

            foreach (DataRow row in datarows)
            {
                Console.WriteLine($"ID: {row[0]}, Name: {row[1]}, Price: {row[2]}, Discontinued: {row[3]}, Date: {row[4]}");
            }

            // 結果は:
            // ID: 9, Name: Product Name_9, Price: 1230000, Discontinued: False, Date: 2018 / 10 / 21 15:27:08
            // ID: 10, Name: Product Name_10, Price: 1353000, Discontinued: True, Date: 2018 / 10 / 22 15:27:08
            // ID: 11, Name: Product Name_11, Price: 1476000, Discontinued: False, Date: 2018 / 10 / 23 15:27:08
            // ID: 12, Name: Product Name_12, Price: 1599000, Discontinued: True, Date: 2018 / 10 / 24 15:27:08
            // ID: 13, Name: Product Name_13, Price: 1722000, Discontinued: False, Date: 2018 / 10 / 25 15:27:08
            // ID: 14, Name: Product Name_14, Price: 1845000, Discontinued: True, Date: 2018 / 10 / 26 15:27:08
        }

        // データソース用の DataSet を作成
        protected static DataSet CreateDataSource()
        {
            DataTable dt = new DataTable();
            DataRow dr;

            dt.Columns.Add(new DataColumn("ID", typeof(Int32)));
            dt.Columns.Add(new DataColumn("Name", typeof(string)));
            dt.Columns.Add(new DataColumn("Price", typeof(decimal)));
            dt.Columns.Add(new DataColumn("Discontinued", typeof(bool)));
            dt.Columns.Add(new DataColumn("Date", typeof(DateTime)));

            for (int i = 0; i < 15; i++)
            {
                dr = dt.NewRow();
                dr["ID"] = i;
                dr["Name"] = "Product Name_" + i.ToString();
                dr["Price"] = 123000 * (i + 1);
                dr["Discontinued"] = (i % 2 == 0) ? true : false;
                dr["Date"] = DateTime.Now.AddDays(i - 15);
                dt.Rows.Add(dr);
            }

            DataSet ds = new DataSet();
            ds.Tables.Add(dt);
            return ds;
        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/27 22:42

    返信遅くなってしまいすみません。参考にさせていただきます。
    私事で大変申し訳ないのですが、私用のため作業が明日出来ないため、明後日以降改めて連絡させていただければと思います。

    キャンセル

  • 2018/10/29 15:30

    返信が遅くなってしまい申し訳ございません.
    追記の方でいただいたコードを参考にさせていただき,スキーマ付きのXMLファイルを作成することができました.ありがとうございます.

    キャンセル

  • 2018/10/29 21:01

    解決したなら放置しておかないでクローズしてください。

    キャンセル

+2

.Where(n=>n.date < date && n.date > date.AddDays(-7))


こんな感じ。

XMLなら、オブジェクトにシリアライズすればいいだけでは?って感じだけど。
なんで、Datasetをつかうの?とか思うけど。
https://improveandrepeat.com/2017/08/paste-xml-as-class-in-visual-studio-2017/

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/26 12:17

    回答ありがとうございます.
    オブジェクトへのシリアライズについても検討させていただきます.
    リンクも貼っていただきありがとうございます.

    キャンセル

+1

こういうことでしょうか?

DateTime start = new DateTime(2018, 10, 25);
DateTime end   = start.AddDays(7);

var rows = dt.AsEnumerable().Where(row => row["日時"] is DateTime 
                                       && start <= (DateTime)row["日時"]
                                       && (DateTime)row["日時"] <= end);

抽出条件の部分は下記のように拡張メソッドを作るとよいかもしれません。

public static bool Between<T>(this T value, T from, T to) where T : IComparable<T>
{
    return value.CompareTo(from) >= 0 && value.CompareTo(to) <= 0;
}


DateTime start = new DateTime(2018, 10, 25);
DateTime end   = start.AddDays(7);

var rows = dt.AsEnumerable().Where(row => ((DateTime)row["日時"]).Between(start, end));

 実際のソースを見た後での追記

DataTableである必要があるのでしょうか?
やはりオブジェクトにデシリアライズするほうが、扱いやすく拡張もしやすいように思われます。
例えば、↓こんなクラスをつくり・・・

[XmlType("data")]
public class Plan
{
    [XmlElement("日時")]
    public DateTime ScheduledDate { get; set; }
    [XmlElement("場所")]
    public string Place { get; set; }
    [XmlElement("メモ")]
    public string Note { get; set; }
}


↓こんなユーティリティクラスを用意しといて・・・

public static class XmlUtility
{
    public static T ConvertToObject<T>(string path, string xmlRoot = null) where T : class
    {
        T deserializedObject = default(T);

        if (!File.Exists(path))
        {
            return deserializedObject;
        }

        XmlSerializer serializer = string.IsNullOrWhiteSpace(xmlRoot) ? new XmlSerializer(typeof(T)) : new XmlSerializer(typeof(T), new XmlRootAttribute(xmlRoot));
        using (var reader = new StreamReader(path))
        {
            deserializedObject = serializer.Deserialize(reader) as T;
        }

        return deserializedObject;
    }
}


↓こんな感じにする

public partial class Display : Form
{
    private string DataFileName = System.IO.Path.Combine(Application.StartupPath, "XMLFile1.xml");

    public Display()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, EventArgs e)
    {
        // デシリアライズ
        List<Plan> plans = XmlUtility.ConvertToObject<List<Plan>>(DataFileName, "DocumentElement");
        if (plans != null)
        {
            // 現在から1週間分に絞り込み
            DateTime start = DateTime.Now;
            DateTime end   = start.AddDays(7);
            var thisWeekPlans = plans.Where(p => p.ScheduledDate.Between(start, end)).ToList();
            // DataGridViewに表示したいと想像
            dataGridView.DataSource = thisWeekPlans;
        }
    }
}


また、XMLの日時の書式は"YYYY-MM-DDThh:mm:ss"である必要があります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/30 11:50

    返信が遅れてしまい申し訳ございません.
    無事,オブジェクトにデシリアライズすることが出来ました.
    ユーティリティクラスのif文の処理でnullを返している気がしたので修正しておきました.
    お付き合いいただき,ありがとうございました.

    キャンセル

  • 2018/10/30 18:02

    >ユーティリティクラスのif文の処理でnullを返している気がしたので修正しておきました。
    おお・・・たしかに・・・。すみません・・・。
    正しくは、
    × if (File.Exists(path))
    〇 if (!File.Exists(path))
    でした。引数に指定されたパス(ファイル)が存在しない場合にnullを返すようにしています。
    回答文も修正しておきました。

    キャンセル

  • 2018/10/31 11:34

    修正ありがとうございます.

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る