net - datatable.rows c#
Usando DataTable en.NET Core (6)
@meziantou. Me encanta tu respuesta, pero hay un error en tu implementación. El primer problema fue que se llamó a MoveNext en el constructor, lo que provocaría que cualquier iteración del lector omita siempre el primer valor. Una vez que eliminé eso, descubrí por qué se hizo eso en primer lugar. Cambié GetFieldType para utilizar la información de tipo, en lugar de leer el tipo del valor, que resolvió ese problema. De nuevo, realmente excelente respuesta de usted. Gracias por tu publicación. Aquí está mi versión fija de 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();
}
}
Tengo un procedimiento almacenado en SQL Server que acepta un tipo de tabla definido por el usuario. Estoy siguiendo la respuesta de esta publicación Inserción masiva de la lista C # en SQL Server en varias tablas con constantes de clave externa sobre cómo enviar una DataTable a un procedimiento almacenado en SQL.
Pero cuando creo DataTable table = new DataTable();
Me sale un error que DataTable does not contain a constructor that takes 0 arguments
.
Encontré este https://github.com/VahidN/EPPlus.Core/issues/4 que básicamente dice que DataTable
ya no es compatible con .NET Core. ¿Y ahora que? ¿Cómo creo una DataTable (o cuál es su reemplazo)? ¿Cómo envío un tipo de tabla definido por el usuario a SQL Server en .NET Core?
DataTable ahora es compatible con .NET CORE 2.0. Vea mi respuesta en .Net Core cómo implementar SQLAdapter ./ Función DataTable . El código de ejemplo a continuación funciona 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;
}
Desarrollador82,
Estoy en la misma situación en la que quiero usar el núcleo de .NET pero la falta de disponibilidad de datatable, el conjunto de datos es un fastidio. ya que está haciendo referencia a una publicación que usa una Lista, pensé que tal vez el objetivo es conseguir que la Lista de C # en la base de datos sea la forma más limpia posible. Si ese es el objetivo, entonces esto podría ayudar.
He utilizado Dapper ubicado aquí en varios proyectos. Es compatible con .netcore. a continuación se muestra una pequeña aplicación de consola que toma una lista c # poblada y la inserta en el DB y luego emite un Seleccionar en esa tabla para escribir los resultados en la consola.
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;
}
}
}
Espero que este código de ejemplo ayude.
gracias.
Hay 2 soluciones para este problema. Uno está utilizando DbDataReader
como @meziantou sugerido en su respuesta, y fue agradable proporcionar un método genérico que convertirá un IEnumerable<T>
en un DbDataReader
.
La otra solución que encontré fue el uso de SqlDataRecord
, así que lo estoy escribiendo aquí (usa lo que mejor se adapte a tus necesidades):
Tabla de 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]
Tipo de tabla definida por el usuario:
CREATE TYPE [dbo].[TblUser] AS TABLE(
[FirstName] [nvarchar](50) NULL,
[LastNAme] [nvarchar](50) NULL
)
Código de núcleo .NET:
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();
}
Puede usar un DbDataReader
como el valor del parámetro SQL. Entonces, la idea es convertir un IEnumerable<T>
a un 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();
}
}
Entonces, puedes usar esta clase:
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 };
}
}
}
Los 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
Por cierto, escribí una publicación de blog sobre ObjectDataReader<T>
Tuve el mismo problema, que no puede crear una DataTable
y, por lo tanto, solo volcarla en una hoja.
La falta de compatibilidad con DataTable
en Core lo fuerza a crear objetos muy tipados y luego recorrerlos y asignarlos a la salida de EPPlus.
Entonces un ejemplo muy simple es:
// 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++;
}
}
Por lo tanto, está tomando una fuente enumerable y un objeto fuertemente tipado, que puede hacer directamente desde una consulta de EF, o un modelo de vista o cualquier otra cosa y luego haciendo un recorrido para mapearlo.
Lo he usado y el rendimiento aparece, para un usuario final, a la par con el método DataTable
. No he inspeccionado la fuente, pero no me sorprendería si el método DataTable
hace lo mismo internamente y recorre cada fila.
Podría crear un método de extensión para usar genéricos para pasar la lista de objetos y usar la reflexión para mapearlo correctamente ... Tal vez eche un vistazo al proyecto y vea si puedo contribuir.
Editar para agregar:
En .NET Core, parece que, desde el rastreador de problemas de GitHub, la compatibilidad con DataTable
es bastante baja en la lista de prioridades, y no lo espera en el corto plazo. Creo que también es un punto filosófico, ya que el concepto generalmente es tratar de usar objetos fuertemente tipados. Por lo tanto, solía ser que podía ejecutar una consulta SQL en un DataTable
y ejecutar con eso ... Ahora, debe ejecutar esa consulta en un modelo directamente mapeado a una tabla con Entity Framework
través de un DbSet
, o con ModelBinding
y pasando un tipo a la consulta.
Luego tiene un IQueryable<T>
que sirve como su reemplazo fuertemente tipado a DataTables
. Para ser justos con este enfoque, para el 99% de los casos es un enfoque válido y mejor ... Sin embargo, siempre habrá momentos en los que la falta de DataTables
causará problemas y será necesario DataTables
.
Editar más
En ADO.NET
puede convertir un datareader
a una lista de objetos fuertemente tipada: ¿Cómo puedo convertir DataReader fácilmente en List <T>? por nombrar solo un ejemplo. Con esta lista puedes hacer tu mapeo desde allí.
Si desea / debe usar ASP.NET Core
que se dirige a ASP.NET Core framework
, tendrá que hacer esto. Si puede orientar Net 4.5 utilizando un proyecto Core, podrá usar System.Data
y tener DataTables
atrás, la única advertencia es que luego tendrá que usar servidores Windows e IIS.
¿Realmente necesitas el .Net Core
framework completo? ¿Necesitas alojar en Linux? Si no, y realmente necesita DataTables, solo apunte al Framework anterior.