web-dev-qa-db-fra.com

Utilisation de DataTable dans .NET Core

J'ai une procédure stockée dans SQL Server qui accepte un type de table défini par l'utilisateur. Je suis la réponse de cet article Insertion en bloc de la liste C # dans SQL Server dans plusieurs tables avec des contraintes de clé étrangère sur la procédure d'envoi d'un DataTable à une procédure stockée dans SQL.

Mais lorsque je crée DataTable table = new DataTable();, je reçois une erreur qui DataTable does not contain a constructor that takes 0 arguments.

J'ai trouvé ceci https://github.com/VahidN/EPPlus.Core/issues/4 qui indique en gros que DataTable n'est plus pris en charge dans .NET Core. Et maintenant? Comment créer un DataTable (ou son remplacement)? Comment envoyer un type de table défini par l'utilisateur à SQL Server sous .NET Core?

11
developer82

DataTable est maintenant pris en charge dans .NET CORE 2.0. Voir ma réponse à .Net Core comment implémenter SQLAdapter ./ Fonction DataTable . Exemple de code ci-dessous fonctionne en 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;
}
11
Joe Healy

Vous pouvez utiliser DbDataReader comme valeur du paramètre SQL. L’idée est donc de convertir un IEnumerable<T> en une 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();
    }
}

Ensuite, vous pouvez utiliser cette classe:

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

Les scripts 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

Au fait, j'ai écrit un blog sur le ObjectDataReader<T>

8
meziantou

Il y a 2 solutions à ce problème. On utilise DbDataReader comme @meziantou l'a suggéré dans sa réponse. Nice devait fournir une méthode générique permettant de convertir un IEnumerable<T> en DbDataReader.

L’autre solution que j’ai trouvée utilisait SqlDataRecord; je l’écris donc ici (utilisez ce que vous jugez utile):

Table 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]

Type de table défini par l'utilisateur:

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

Code .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. J'adore votre réponse, mais votre mise en œuvre présente un bogue de rupture. Le premier problème était que MoveNext était appelé dans le constructeur, ce qui ferait en sorte que toute itération du lecteur ignore toujours la première valeur. Une fois que j'ai enlevé cela, j'ai découvert pourquoi cela avait été fait en premier lieu. J'ai changé GetFieldType pour utiliser les informations de type, plutôt que de lire le type à partir de la valeur, ce qui a résolu le problème. Encore une fois, vraiment excellente réponse de votre part. Merci d'avoir posté. Voici ma version corrigée d'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

J'ai eu le même problème, que vous ne pouvez pas créer un DataTable et donc simplement le déposer dans une feuille.

Le manque de prise en charge de DataTable dans Core vous oblige à créer des objets fortement typés, puis à les faire suivre en boucle et à les mapper vers la sortie de EPPlus.

Donc, un exemple très simple consiste à:

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

Ainsi, vous prenez une source énumérable de et un objet fortement typé, que vous pouvez faire directement à partir d'une requête EF, d'un modèle de vue ou de tout autre élément, puis que vous passez en boucle pour le mapper.

J'ai utilisé cela et la performance apparaît - pour un utilisateur final - à égalité avec la méthode DataTable. Je n'ai pas inspecté la source, mais cela ne me surprendrait pas si la méthode DataTable fait la même chose en interne et parcourt chaque ligne.

Vous pouvez créer une méthode d'extension permettant aux génériques de transmettre la liste d'objets et d'utiliser la réflexion pour la mapper correctement ... Peut-être que je regarderai le projet et verrai si je peux contribuer.

Modifier pour ajouter:

Dans .NET Core, il ressort du traqueur de problèmes GitHub que la prise en charge de DataTable est assez basse dans la liste des priorités et qu’elle ne l’attend pas de si tôt. Je pense que c'est aussi un point philosophique, car le concept est généralement d'utiliser des objets fortement typés. Ainsi, vous pouviez exécuter une requête SQL dans une variable DataTable et exécuter avec celle-ci ... Maintenant, vous devez exécuter cette requête dans un modèle directement mappé sur une table avec Entity Framework via une variable DbSet ou avec ModelBinding un type dans la requête.

Vous avez alors un IQueryable<T> qui sert de votre remplacement fortement typé à DataTables. Pour être juste avec cette approche, dans 99% des cas, il s'agissait d'une approche valide et meilleure ... Cependant, il y aura toujours des moments où l'absence de DataTables causera des problèmes et devra être contournée!

Édition ultérieure

Dans ADO.NET, vous pouvez convertir une datareader en une liste d'objets fortement typée: Comment puis-je convertir facilement DataReader en Liste <T>? pour ne citer qu'un exemple. Avec cette liste, vous pouvez faire votre cartographie à partir de là.

Si vous voulez/devez utiliser ASP.NET Core qui cible ASP.NET Core framework, vous devrez le faire. Si vous pouvez cibler Net 4.5 à l'aide d'un projet principal, vous pourrez alors utiliser System.Data et avoir DataTables de retour, le seul inconvénient étant que vous devrez alors utiliser les serveurs Windows et IIS.

Avez-vous vraiment besoin du framework .Net Core complet? Avez-vous besoin d'héberger sur Linux? Si non, et que vous avez vraiment besoin de DataTables, ciblez uniquement le Framework plus ancien.

1
RemarkLima

Développeur82,

Je suis dans la même situation où je veux utiliser .net core, mais l'indisponibilité de datatable, l'ensemble de données est une déception. Puisque vous faites référence à un article qui utilise une liste, j’ai pensé que l’objectif est peut-être d’obtenir la liste C # dans la base de données de la manière la plus propre possible. Si tel est l'objectif, cela pourrait aider. 

J'ai utilisé Dapper situé ici dans divers projets. Il est soutenu par .netcore. Vous trouverez ci-dessous une petite application de console qui prend une liste c # remplie et l'insère dans la base de données, puis émet une sélection sur cette table pour écrire les résultats dans la console.

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

J'espère que cet exemple de code aide.

je vous remercie.

0
Ed Mendez