web-dev-qa-db-ja.com

.NET CoreでDataTableを使用する

ユーザー定義のテーブルタイプを受け入れるストアドプロシージャがSQL Serverにあります。私はこの投稿からの回答に従っています C#リストからSQL Serverに外部キー制約を持つ複数のテーブルに一括挿入 SQLのストアドプロシージャにDataTableを送信する方法について。

しかし、DataTable table = new DataTable();を作成すると、DataTable does not contain a constructor that takes 0 arguments

私はこれを見つけました https://github.com/VahidN/EPPlus.Core/issues/4 これは基本的にDataTableが.NET Coreでサポートされなくなったことを言います。んで、どうする? DataTableを作成するにはどうすればよいですか(または、その置換は何ですか)。 .NET Core上のSQL Serverにユーザー定義のテーブルタイプを送信するにはどうすればよいですか?

12
developer82

DataTableは、.NET CORE 2.0でサポートされるようになりました。 。Net Core SQLAdapterの実装方法./ DataTable関数 で私の答えを参照してください。以下のサンプルコードは2.0で機能します。

public static DataTable ExecuteDataTableSqlDA(SqlConnection conn, CommandType cmdType, string cmdText, SqlParameter[] cmdParms)
{
   System.Data.DataTable dt = new DataTable();
   System.Data.SqlClient.SqlDataAdapter da = new SqlDataAdapter(cmdText, conn);
   da.Fill(dt);
   return dt;
}
13
Joe Healy

DbDataReaderをSQLパラメーターの値として使用できます。そのため、IEnumerable<T>からDbDataReaderへ。

public class ObjectDataReader<T> : DbDataReader
{
    private bool _iteratorOwned;
    private IEnumerator<T> _iterator;
    private IDictionary<string, int> _propertyNameToOrdinal = new Dictionary<string, int>();
    private IDictionary<int, string> _ordinalToPropertyName = new Dictionary<int, string>();
    private Func<T, object>[] _getPropertyValueFuncs;

    public ObjectDataReader(IEnumerable<T> enumerable)
    {
        if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));

        _iteratorOwned = true;
        _iterator = enumerable.GetEnumerator();
        _iterator.MoveNext();
        Initialize();
    }

    public ObjectDataReader(IEnumerator<T> iterator)
    {
        if (iterator == null) throw new ArgumentNullException(nameof(iterator));

        _iterator = iterator;    
        Initialize();
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && _iteratorOwned)
        {
            if(_iterator != null)
                _iterator.Dispose();
        }

        base.Dispose(disposing);
    }

    private void Initialize()
    {
        int ordinal = 0;
        var properties = typeof(T).GetProperties();
        _getPropertyValueFuncs = new Func<T, object>[properties.Length];
        foreach (var property in properties)
        {
            string propertyName = property.Name;
            _propertyNameToOrdinal.Add(propertyName, ordinal);
            _ordinalToPropertyName.Add(ordinal, propertyName);

            var parameterExpression = Expression.Parameter(typeof(T), "x");
            var func = (Func<T, object>)Expression.Lambda(Expression.Convert(Expression.Property(parameterExpression, propertyName), typeof(object)), parameterExpression).Compile();
            _getPropertyValueFuncs[ordinal] = func;

            ordinal++;
        }
    }

    public override object this[int ordinal] 
    {
        get
        {
            return GetValue(ordinal);
        }
    }

    public override object this[string name]
    {
        get
        {
            return GetValue(GetOrdinal(name));
        }
    }

    public override int Depth => 1;

    public override int FieldCount => _ordinalToPropertyName.Count;

    public override bool HasRows => true;

    public override bool IsClosed
    {
        get
        {
            return _iterator != null;
        }
    }

    public override int RecordsAffected
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    public override bool GetBoolean(int ordinal)
    {
        return (bool)GetValue(ordinal);
    }

    public override byte GetByte(int ordinal)
    {
        return (byte)GetValue(ordinal);
    }

    public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
    {
        throw new NotImplementedException();
    }

    public override char GetChar(int ordinal)
    {
        return (char)GetValue(ordinal);
    }

    public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
    {
        throw new NotImplementedException();
    }

    public override string GetDataTypeName(int ordinal)
    {
        throw new NotImplementedException();
    }

    public override DateTime GetDateTime(int ordinal)
    {
        return (DateTime)GetValue(ordinal);
    }

    public override decimal GetDecimal(int ordinal)
    {
        return (decimal)GetValue(ordinal);
    }

    public override double GetDouble(int ordinal)
    {
        return (double)GetValue(ordinal);
    }

    public override IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public override Type GetFieldType(int ordinal)
    {
        var value = GetValue(ordinal);
        if (value == null)
            return typeof(object);

        return value.GetType();
    }

    public override float GetFloat(int ordinal)
    {
        return (float)GetValue(ordinal);
    }

    public override Guid GetGuid(int ordinal)
    {
        return (Guid)GetValue(ordinal);
    }

    public override short GetInt16(int ordinal)
    {
        return (short)GetValue(ordinal);
    }

    public override int GetInt32(int ordinal)
    {
        return (int)GetValue(ordinal);
    }

    public override long GetInt64(int ordinal)
    {
        return (long)GetValue(ordinal);
    }

    public override string GetName(int ordinal)
    {
        string name;
        if (_ordinalToPropertyName.TryGetValue(ordinal, out name))
            return name;

        return null;
    }

    public override int GetOrdinal(string name)
    {
        int ordinal;
        if (_propertyNameToOrdinal.TryGetValue(name, out ordinal))
            return ordinal;

        return -1;
    }

    public override string GetString(int ordinal)
    {
        return (string)GetValue(ordinal);
    }

    public override object GetValue(int ordinal)
    {
        var func = _getPropertyValueFuncs[ordinal];
        return func(_iterator.Current);
    }

    public override int GetValues(object[] values)
    {
        int max = Math.Min(values.Length, FieldCount);
        for (var i = 0; i < max; i++)
        {
            values[i] = IsDBNull(i) ? DBNull.Value : GetValue(i);
        }

        return max;
    }

    public override bool IsDBNull(int ordinal)
    {
        return GetValue(ordinal) == null;
    }

    public override bool NextResult()
    {
        return false;
    }

    public override bool Read()
    {
        return _iterator.MoveNext();
    }
}

次に、このクラスを使用できます。

static void Main(string[] args)
{
    Console.WriteLine("Hello World!");
    string connectionString = "Server=(local);Database=Sample;Trusted_Connection=True;";

    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();

        using (var command = connection.CreateCommand())
        {
            command.CommandType = System.Data.CommandType.StoredProcedure;
            command.CommandText = "procMergePageView";

            var p1 = command.CreateParameter();
            command.Parameters.Add(p1);    
            p1.ParameterName = "@Display";
            p1.SqlDbType = System.Data.SqlDbType.Structured;
            var items = PageViewTableType.Generate(100);
            using (DbDataReader dr = new ObjectDataReader<PageViewTableType>(items))
            {
                p1.Value = dr;
                command.ExecuteNonQuery();
            }    
        }
    }
}

class PageViewTableType
{
    // Must match the name of the column of the TVP
    public long PageViewID { get; set; }

    // Generate dummy data
    public static IEnumerable<PageViewTableType> Generate(int count)
    {
        for (int i = 0; i < count; i++)
        {
            yield return new PageViewTableType { PageViewID = i };
        }
    }
}

SQLスクリプト:

CREATE TABLE dbo.PageView
(
    PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED,
    PageViewCount BIGINT NOT NULL
);
GO

CREATE TYPE dbo.PageViewTableType AS TABLE
(
    PageViewID BIGINT NOT NULL
);
GO

CREATE PROCEDURE dbo.procMergePageView
    @Display dbo.PageViewTableType READONLY
AS
BEGIN
    MERGE INTO dbo.PageView AS T
    USING @Display AS S
    ON T.PageViewID = S.PageViewID
    WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
    WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END

ところで、私は ObjectDataReader<T>

8
meziantou

この問題には2つの解決策があります。 1つは、@ meziantouが答えで示唆したようにDbDataReaderを使用しており、IEnumerable<T>DbDataReaderに変換する一般的なメソッドを提供できるのは素晴らしいことです。

私が見つけた他の解決策はSqlDataRecordを使用していたので、ここに書き留めています(ニーズに合ったものを使用してください):

SQL Serverテーブル:

CREATE TABLE [dbo].[Users](
    [UserId] [int] IDENTITY(1,1) NOT NULL,
    [FirstName] [nvarchar](50) NULL,
    [LastNAme] [nvarchar](50) NULL,
 CONSTRAINT [PK_USers] PRIMARY KEY CLUSTERED 
(
    [UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ユーザー定義の表タイプ:

CREATE TYPE [dbo].[TblUser] AS TABLE(
    [FirstName] [nvarchar](50) NULL,
    [LastNAme] [nvarchar](50) NULL
)

.NET Coreコード:

var db = new SqlConnection("Server=localhost; Database=Test; User Id=test; Password=123456;");

List<SqlDataRecord> users = new List<SqlDataRecord>();

SqlMetaData mDataFirstName = new SqlMetaData("FirstName", SqlDbType.NVarChar, 50);
SqlMetaData mDataLastName = new SqlMetaData("LastName", SqlDbType.NVarChar, 50);

SqlDataRecord user1 = new SqlDataRecord(new []{ mDataFirstName, mDataLastName });
user1.SetString(0, "Ophir");
user1.SetString(1, "Oren");
users.Add(user1);

SqlParameter param = new SqlParameter("@Users", SqlDbType.Structured)
{
    TypeName = "TblUser",
    Value = users
};

Dictionary<string, object> values = new Dictionary<string, object>();
values.Add("@Users", param);


db.Open();
using (var command = db.CreateCommand())
{
    command.CommandType = System.Data.CommandType.StoredProcedure;
    command.CommandText = "stp_Users_Insert";

    var p1 = command.CreateParameter();
    command.Parameters.Add(p1);
    p1.ParameterName = "@Users";
    p1.SqlDbType = System.Data.SqlDbType.Structured;
    p1.Value = users;
    command.ExecuteNonQuery();
}
1
developer82

@meziantou。私はあなたの答えを気に入っていますが、実装には破壊的なバグがあります。最初の問題は、MoveNextがコンストラクターで呼び出され、リーダーの反復で常に最初の値がスキップされることでした。それを削除すると、最初にそれが行われた理由を発見しました。値から型を読み取るのではなく、型情報を使用するようにGetFieldTypeを変更して、その問題を解決しました。繰り返しますが、あなたからの本当に素晴らしい答えです。投稿していただきありがとうございます。これが、ObjectDataReaderの修正済みバージョンです。

    public class ObjectDataReader<T> : DbDataReader
    {   
        private bool _iteratorOwned;
        private IEnumerator<T> _iterator;
        private IDictionary<string, int> _propertyNameToOrdinal = new Dictionary<string, int>();
        private IDictionary<int, string> _ordinalToPropertyName = new Dictionary<int, string>();
        private PropertyInfoContainer[] _propertyInfos;

        class PropertyInfoContainer
        {
            public Func<T, object> EvaluatePropertyFunction { get; set; }
            public Type PropertyType { get; set; }
            public string PropertyName { get; set; }
            public PropertyInfoContainer(string propertyName
                , Type propertyType
                , Func<T, object> evaluatePropertyFunction)
            {
                this.PropertyName = propertyName;
                this.PropertyType = propertyType;
                this.EvaluatePropertyFunction = evaluatePropertyFunction;
            }
        }

        public ObjectDataReader(IEnumerable<T> enumerable)
        {
            if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));

            _iteratorOwned = true;
            _iterator = enumerable.GetEnumerator();
            //_iterator.MoveNext();
            Initialize();
        }

        public ObjectDataReader(IEnumerator<T> iterator)
        {
            if (iterator == null) throw new ArgumentNullException(nameof(iterator));

            _iterator = iterator;
            Initialize();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && _iteratorOwned)
            {
                if (_iterator != null)
                    _iterator.Dispose();
            }

            base.Dispose(disposing);
        }

        private void Initialize()
        {
            int ordinal = 0;
            var properties = typeof(T).GetProperties();
            _propertyInfos = new PropertyInfoContainer[properties.Length];
            foreach (var property in properties)
            {
                string propertyName = property.Name;
                _propertyNameToOrdinal.Add(propertyName, ordinal);
                _ordinalToPropertyName.Add(ordinal, propertyName);

                var parameterExpression = Expression.Parameter(typeof(T), "x");
                var func = (Func<T, object>)Expression.Lambda(Expression.Convert(Expression.Property(parameterExpression, propertyName), typeof(object)), parameterExpression).Compile();
                _propertyInfos[ordinal] = new PropertyInfoContainer(property.Name
                    , property.PropertyType
                    , func);

                ordinal++;
            }
        }

        public override object this[int ordinal]
        {
            get
            {
                return GetValue(ordinal);
            }
        }

        public override object this[string name]
        {
            get
            {
                return GetValue(GetOrdinal(name));
            }
        }

        public override int Depth => 1;

        public override int FieldCount => _ordinalToPropertyName.Count;

        public override bool HasRows => true;

        public override bool IsClosed
        {
            get
            {
                return _iterator != null;
            }
        }

        public override int RecordsAffected
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override bool GetBoolean(int ordinal)
        {
            return (bool)GetValue(ordinal);
        }

        public override byte GetByte(int ordinal)
        {
            return (byte)GetValue(ordinal);
        }

        public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
        {
            throw new NotImplementedException();
        }

        public override char GetChar(int ordinal)
        {
            return (char)GetValue(ordinal);
        }

        public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
        {
            throw new NotImplementedException();
        }

        public override string GetDataTypeName(int ordinal)
        {
            throw new NotImplementedException();
        }

        public override DateTime GetDateTime(int ordinal)
        {
            return (DateTime)GetValue(ordinal);
        }

        public override decimal GetDecimal(int ordinal)
        {
            return (decimal)GetValue(ordinal);
        }

        public override double GetDouble(int ordinal)
        {
            return (double)GetValue(ordinal);
        }

        public override IEnumerator GetEnumerator()
        {
            throw new NotImplementedException();
        }

        public override Type GetFieldType(int ordinal)
        {    
            // cannot handle nullable types, so get underlying type
            var propertyType =
                                Nullable.GetUnderlyingType(_propertyInfos[ordinal].PropertyType) ?? _propertyInfos[ordinal].PropertyType;

            return propertyType;
        }

        public override float GetFloat(int ordinal)
        {
            return (float)GetValue(ordinal);
        }

        public override Guid GetGuid(int ordinal)
        {
            return (Guid)GetValue(ordinal);
        }

        public override short GetInt16(int ordinal)
        {
            return (short)GetValue(ordinal);
        }

        public override int GetInt32(int ordinal)
        {
            return (int)GetValue(ordinal);
        }

        public override long GetInt64(int ordinal)
        {
            return (long)GetValue(ordinal);
        }

        public override string GetName(int ordinal)
        {
            string name;
            if (_ordinalToPropertyName.TryGetValue(ordinal, out name))
                return name;

            return null;
        }

        public override int GetOrdinal(string name)
        {
            int ordinal;
            if (_propertyNameToOrdinal.TryGetValue(name, out ordinal))
                return ordinal;

            return -1;
        }

        public override string GetString(int ordinal)
        {
            return (string)GetValue(ordinal);
        }

        public override object GetValue(int ordinal)
        {
            var func = _propertyInfos[ordinal].EvaluatePropertyFunction;
            return func(_iterator.Current);
        }

        public override int GetValues(object[] values)
        {
            int max = Math.Min(values.Length, FieldCount);
            for (var i = 0; i < max; i++)
            {
                values[i] = IsDBNull(i) ? DBNull.Value : GetValue(i);
            }

            return max;
        }

        public override bool IsDBNull(int ordinal)
        {
            return GetValue(ordinal) == null;
        }

        public override bool NextResult()
        {
            return false;
        }

        public override bool Read()
        {
            return _iterator.MoveNext();
        }
    }
1
Eric

Developer82、

私は.netコアを使用したいのと同じ状況にいますが、データテーブル、データセットが利用できないのは残念です。あなたはリストを使用する投稿を参照しているので、おそらくC#リストをできる限りきれいな方法でデータベースに入れることが目標だと思いました。それが目標であれば、これが役立つかもしれません。

here にあるDapperをさまざまなプロジェクトで使用しました。 int .netcoreでサポートされています。以下は、設定済みのc#リストを取得してDBに挿入し、そのテーブルでSelectを発行して結果をコンソールに書き込む小さなコンソールアプリです。

using System;
using System.Data;
using Dapper;
using System.Data.Common;
using System.Data.SqlClient;
using System.Collections.Generic;

namespace TestConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            List<DataItemDTO> dataItems = GetDataItems();

            var _selectSql = @"SELECT CustomerId, Name, BalanceDue from [dbo].[CustomerAccount]";

            var _insertSql = @"INSERT INTO [dbo].[CustomerAccount]
                                       ([CustomerId]
                                       ,[Name]
                                       ,[BalanceDue])
                                 VALUES
                                       (@CustomerId
                                       ,@Name
                                       ,@BalanceDue)";



            using (IDbConnection cn = new SqlConnection(@"Server=localhost\xxxxxxx;Database=xxxxdb;Trusted_Connection=True;"))
            {
                var rows = cn.Execute(_insertSql, dataItems,null,null,null );

                dataItems.Clear();

                var results = cn.Query<DataItemDTO>(_selectSql);

                foreach (var item in results)
                {
                    Console.WriteLine("CustomerId: {0} Name {1} BalanceDue {2}", item.CustomerId.ToString(), item.Name, item.BalanceDue.ToString());
                }


            }

            Console.WriteLine("Press any Key");
            Console.ReadKey();

        }

        private static List<DataItemDTO> GetDataItems()
        {
            List<DataItemDTO> items = new List<DataItemDTO>();

            items.Add(new DataItemDTO() { CustomerId = 1, Name = "abc1", BalanceDue = 11.58 });
            items.Add(new DataItemDTO() { CustomerId = 2, Name = "abc2", BalanceDue = 21.57 });
            items.Add(new DataItemDTO() { CustomerId = 3, Name = "abc3", BalanceDue = 31.56 });
            items.Add(new DataItemDTO() { CustomerId = 4, Name = "abc4", BalanceDue = 41.55 });
            items.Add(new DataItemDTO() { CustomerId = 5, Name = "abc5", BalanceDue = 51.54 });
            items.Add(new DataItemDTO() { CustomerId = 6, Name = "abc6", BalanceDue = 61.53 });
            items.Add(new DataItemDTO() { CustomerId = 7, Name = "abc7", BalanceDue = 71.52 });
            items.Add(new DataItemDTO() { CustomerId = 8, Name = "abc8", BalanceDue = 81.51 });

            return items;
        }
    }
}

このコードサンプルが役立つことを願っています。

ありがとうございました。

0
Ed Mendez

同じ問題がありました。DataTableを作成できないため、シートにダンプするだけです。

CoreにDataTableがサポートされていないため、厳密に型指定されたオブジェクトを作成し、ループしてEPPlusの出力にマップする必要があります。

したがって、非常に簡単な例は次のとおりです。

// Get your data directly from EF,
// or from whatever other source into a list,
// or Enumerable of the type
List<MyEntity> data = _whateverService.GetData();

using (ExcelPackage pck = new ExcelPackage())
{
   // Create a new sheet
   var newSheet = pck.Workbook.Worksheets.Add("Sheet 1");
   // Set the header:
   newSheet.Cells["A1"].Value = "Column 1 - Erm ID?";
   newSheet.Cells["B1"].Value = "Column 2 - Some data";
   newSheet.Cells["C1"].Value = "Column 3 - Other data";

  row = 2;
  foreach (var datarow in data)
  {
      // Set the data:
      newSheet.Cells["A" + row].Value = datarow.Id;
      newSheet.Cells["B" + row].Value = datarow.Column2;
      newSheet.Cells["C" + row].Value = datarow.Cilumn3;
      row++;
  }
}

つまり、列挙可能なソースの強く型付けされたオブジェクトを取得します。これは、EFクエリ、またはビューモデルなどから直接実行でき、ループしてマップします。

私はこれを使用しましたが、パフォーマンスは、エンドユーザーにはDataTableメソッドと同等に見えます。私はソースを検査していませんが、DataTableメソッドが内部で同じことを行い、各行をループしているだけでも驚くことはありません。

ジェネリックを使用してオブジェクトリストを渡し、リフレクションを使用してそれを正しくマッピングするための拡張メソッドを作成することができます。プロジェクトを見て、貢献できるかどうかを確認します。

追加して編集:

.NET Coreでは、GitHubの問題トラッカーから、DataTableのサポートは優先順位リストでかなり低く、すぐには期待できないと思われます。概念は一般に強く型付けされたオブジェクトを使用しようとするので、哲学的なポイントでもあると思います。そのため、以前はDataTableに対してSQLクエリを実行し、それを使用して実行することができました...ここで、クエリをEntity Frameworkを使用してテーブルに直接マッピングするモデルに実行する必要がありますDbSetを介して、またはModelBindingを使用して、クエリに型を渡します。

その後、DataTablesの厳密に型指定された置換として機能するIQueryable<T>があります。このアプローチを公平にするために、99%のケースでは有効でより良いアプローチです...しかし、DataTablesの欠如は常に問題を引き起こし、回避する必要があります。

さらに編集する

ADO.NETでは、datareaderをオブジェクトの厳密に型指定されたリストに変換できます。 DataReaderをList <T>に簡単に変換するにはどうすればよいですか 例を挙げます。このリストを使用して、そこからマッピングを作成できます。

ASP.NET CoreをターゲットとするASP.NET Core frameworkを使用する必要がある場合は、これを行う必要があります。 Coreプロジェクトを使用してNet 4.5をターゲットにできる場合、System.Dataを使用し、DataTablesを戻すことができます。唯一の注意点は、WindowsサーバーとIISを使用する必要があることです。

完全な.Net Coreフレームワークが本当に必要ですか? Linuxでホストする必要がありますか?いいえ、DataTableが本当に必要な場合は、古いフレームワークをターゲットにします。

0
RemarkLima