GUID外部キーを持つSqlBulkCopy XML

c# guid sqlbulkcopy uniqueidentifier xml

質問

私は、XMLファイルとSqlBulkCopyを使用してデータを挿入しようとしています。宛先テーブルは、以下のように設定された時系列テーブルである

create table TimeSeries (
    Id          uniqueidentifier constraint DF_TimeSeries_Id default (newid()) not null,
    ObjectId    uniqueidentifier not null,
    [Date]      datetime not null,
    Value       float(53) not null,
    [Type]      varchar (4) not null,
    [Source]    varchar (4) not null,
    LastUpdate  datetime constraint DF_TimeSeries_LastUpdate default (getdate()) not null,
    TypeIndex   smallint constraint DF_TimeSeries_TypeIndex default (0) not null,
    constraint PK_TimeSeries primary key clustered ([Date] asc, ObjectId asc, [Type] asc, [Source] asc, TypeIndex asc) with (fillfactor = 80)
);

go
create nonclustered index [IX_TimeSeries_ObjectId_Type_Date_Source]
    on TimeSeries(ObjectId asc, [Type] asc, [Date] asc, [Source] asc)
    include(Value) with (fillfactor = 80);


go
create nonclustered index [IX_TimeSeries_ObjectId_Date]
    on TimeSeries(ObjectId asc, [Date] asc)
    include(Value) with (fillfactor = 80);

go
create table Beacons
(
    BeaconId uniqueidentifier not null default newid(), 
    [Description] varchar(50) not null, 
    LocationX float not null,
    LocationY float not null,
    Altitude float not null
    constraint PK_Beacons primary key clustered (BeaconId)
)
go
create index IX_Beacons on Beacons (BeaconId)

go
create table SnowGauges
(
    SnowGaugeId uniqueidentifier not null default newid(), 
    [Description] varchar(50) not null
    constraint PK_SnowGauges primary key clustered (SnowGaugeId)
)
go
create index IX_SnowGauges on SnowGauges (SnowGaugeId)


go
insert into Beacons ([Description], LocationX, LocationY, Altitude)
values ('Dunkery', 51.162, -3.586, 519), ('Prestwich', 53.527, -2.279, 76)
insert into SnowGauges ([Description]) values ('Val d''Isère')

select * from Beacons
select * from SnowGauges

ご覧のとおり、私はTimeSeriesに任意の種類の時系列を保存したいと思います。これは、温度、圧力、生物学的データなどである可能性があります。いずれにしても、私は、一意の識別子、ソース、およびタイプによって時系列を識別することができます。この一意の識別子は任意のテーブルを参照できるため、ObjectIdに外部キーは設定されていません。

このスクリプトの最後に2つのビーコンと1つのスノーゲージを挿入し、時系列を記入したいと思います。これを行うXMLファイルの形式は次のとおりです。

<?xml version="1.0" encoding="utf-8" ?>
<TimeSeries>
<TimeSeries ObjectId="186CA33E-AC1C-4220-81DE-C7CD32F40C1A" Date="09/06/2013 07:00:00" Value="9.2" Source = "Met Office" Type = "Temperature"/>
<TimeSeries ObjectId="186CA33E-AC1C-4220-81DE-C7CD32F40C1A" Date="09/06/2013 10:00:00" Value="8.8" Source = "Met Office" Type = "Temperature"/>
<TimeSeries ObjectId="186CA33E-AC1C-4220-81DE-C7CD32F40C1A" Date="09/06/2013 13:00:00" Value="8.7" Source = "Met Office" Type = "Temperature"/>
<TimeSeries ObjectId="186CA33E-AC1C-4220-81DE-C7CD32F40C1A" Date="09/06/2013 07:00:00" Value="1" Source = "Met Office" Type = "UV"/>
<TimeSeries ObjectId="186CA33E-AC1C-4220-81DE-C7CD32F40C1A" Date="09/06/2013 10:00:00" Value="3" Source = "Met Office" Type = "UV"/>
<TimeSeries ObjectId="186CA33E-AC1C-4220-81DE-C7CD32F40C1A" Date="09/06/2013 13:00:00" Value="5" Source = "Met Office" Type = "UV"/>
<TimeSeries ObjectId="AFB81E51-18B0-4696-9C2F-E6E9EEC1B647" Date="09/06/2013 07:00:00" Value="5.8" Source = "Met Office" Type = "Temperature"/>
<TimeSeries ObjectId="AFB81E51-18B0-4696-9C2F-E6E9EEC1B647" Date="09/06/2013 10:00:00" Value="6.3" Source = "Met Office" Type = "Temperature"/>
<TimeSeries ObjectId="AFB81E51-18B0-4696-9C2F-E6E9EEC1B647" Date="09/06/2013 13:00:00" Value="6.5" Source = "Met Office" Type = "Temperature"/>
<TimeSeries ObjectId="50E52A2B-D719-4341-A451-110D0874D26D" Date="07/06/2013 00:00:00" Value="80.5" Source = "Meteo France" Type = "SnowMeter"/>
<TimeSeries ObjectId="50E52A2B-D719-4341-A451-110D0874D26D" Date="08/06/2013 00:00:00" Value="80.5" Source = "Meteo France" Type = "SnowMeter"/>
</TimeSeries>

最初のスクリプトを実行すると、別のObjectIdを持つことが予想され、XMLファイルでそれらを更新する必要があります。だからそこから、すべてがまっすぐ進むべきであり、単純なC#プログラムはデータを挿入する仕事をするべきです。今すぐC#コードを見てみましょう:

using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;

namespace XMLBulkInsert
{
    class Program
    {
        const string XMLFILE_PATH = @"C:\Workspaces\Ws1\R\TimeSeries\TimeSeries.xml";
        const string CONNECTION_STRING = @"Server=RISK1;Database=DevStat;Trusted_Connection=True;";

        static void Main(string[] args)
        {
            StreamReader xmlFile = new StreamReader(XMLFILE_PATH);
            DataSet ds = new DataSet();

            Console.Write("Read file... ");
            ds.ReadXml(xmlFile);
            DataTable sourceData = ds.Tables[0];
            Console.WriteLine("Done !");

            using (SqlConnection sourceConnection = new SqlConnection(CONNECTION_STRING))
            {
                sourceConnection.Open();
                using (SqlBulkCopy bulkCopy = new SqlBulkCopy(sourceConnection.ConnectionString))
                {
                    bulkCopy.ColumnMappings.Add("ObjectId", "ObjectId");
                    bulkCopy.ColumnMappings.Add("Date", "Date");
                    bulkCopy.ColumnMappings.Add("Value", "Value");
                    bulkCopy.ColumnMappings.Add("Source", "Source");
                    bulkCopy.ColumnMappings.Add("Type", "Type");
                    bulkCopy.DestinationTableName = "TimeSeries";

                    try
                    {
                        Console.Write("Insert data... ");
                        bulkCopy.WriteToServer(sourceData);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                    finally
                    {
                        xmlFile.Close();
                        sourceConnection.Close();
                    }
                }
            }

            Console.WriteLine("Insertion completed, please Press Enter...");
            Console.ReadLine();
        }

    }
}

このプログラムを実行すると、次の例外が返されます。 "データソースのString型の指定された値を、指定されたターゲット列のuniqueidentifier型に変換することはできません。"列を一意の識別子にするようにマッピングを設定する方法はないようです。私もこのコードを挿入しようとしましたds.Tables[0].Columns[0].DataType = typeof(Guid);しかし、成功していないと、テーブルが行データを持つと.Netは列のタイプを変更できません。

私はSQlBulkCopyで高い例外を抱えていましたが、今はちょっと立ち往生しています。私は何百万と何百万ものXML形式のデータを持っており、この一意の識別子のためにそれらを挿入することはできません。

ユニークな識別子を受け入れるためにこのクラスを設定する方法を誰かが知っていますか?

受け入れられた回答

約3億行のコメントがあると、私はDataTableについて忘れてしまい DataTable 。そのデータを一度に読み込む必要はありません。理想的には、それを要素ごとに解析して、データをIDataReaderとして公開することです。

幸いにも、そのためのいくつかのユーティリティが存在します。まず、データを解析しましょう。基本的に各行は次のとおりです。

class TimeSeries
{
    public Guid ObjectId { get; set; }
    public DateTime Date { get; set; }
    public string Source { get; set; }
    public string Type { get; set; }
    public decimal Value { get; set; }
}

次のような要素ベースのリーダーを書くことができます:

static IEnumerable<TimeSeries> ReadTimeSeries(TextReader source)
{
    using (var reader = XmlReader.Create(source, new XmlReaderSettings {
                     IgnoreWhitespace = true }))
    {
        reader.MoveToContent();
        reader.ReadStartElement("TimeSeries");
        while(reader.Read() && reader.NodeType == XmlNodeType.Element
                    && reader.Depth == 1)
        {
            using (var subtree = reader.ReadSubtree())
            {
                var el = XElement.Load(subtree);
                var obj = new TimeSeries
                {
                    ObjectId = (Guid) el.Attribute("ObjectId"),
                    // note: datetime is not xml format; need to parse - this
                    // should probably be more explicit
                    Date = DateTime.Parse((string) el.Attribute("Date")),
                    Source = (string) el.Attribute("Source"),
                    Type = (string)el.Attribute("Type"),
                    Value = (decimal)el.Attribute("Value")
                };
                yield return obj;
            }
        }
    }
}

これは "反復子ブロック"であり、遅延スプーリングであることに注意してください。すべてのデータを一度に読み込むわけではありません。

次に、 IEnumerable<T>を消費し、 IDataReaderとして公開するAPIが必要です.FastMemberはこれを(そして他の多くのことも)正確に行います。だから我々はちょうど書くことができます:

using(var bcp = new SqlBulkCopy(connection))
using(var objectReader = ObjectReader.Create(ReadTimeSeries(source)))
{
    bcp.DestinationTableName = "SomeTable";
    bcp.WriteToServer(objectReader);
}

sourceTextReaderFile.OpenText

using(var source = File.OpenText(path))
using(var bcp = new SqlBulkCopy(connection))
using(var objectReader = ObjectReader.Create(ReadTimeSeries(source)))
{
    bcp.DestinationTableName = "SomeTable";
    bcp.WriteToServer(objectReader);
}

列の順序を制御する場合は、 bcp.ColumnMappingsを使用できますが、 IDataReaderで内部的に行うほうが便利です。

using(var objectReader = ObjectReader.Create(
    ReadTimeSeries(source, "ObjectId", "Date", "Value" /* etc */)))
{
    bcp.DestinationTableName = "SomeTable";
    bcp.WriteToServer(objectReader);
}

これを自分のコードの一部に使用しDataTable 。たとえデータメモリに収まってても、 DataTable経由よりもはるかに高速です。

しかし、重要なポイントは、現在行われていることを管理することです。



ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ