在.NET Core中使用DataTable

我在SQL Server中有一个存储过程,它接受用户定义的表类型。 我正在按照这篇文章的答案从C#list批量插入SQL Server到具有外键密码的多个表中,如何将DataTable发送到SQL中的存储过程。

但是当我创建DataTable table = new DataTable(); 我收到一个错误, DataTable does not contain a constructor that takes 0 arguments

我发现这个https://github.com/VahidN/EPPlus.Core/issues/4基本上说.NET Core不再支持DataTable 。 那么现在怎么办? 如何创建DataTable(或者它的替代品是什么)? 如何将用户定义的表类型发送到.NET Core上的SQL Server?

.NET CORE 2.0现在支持DataTable。 请参阅我在.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; } 

您可以使用DbDataReader作为SQL参数的值。 因此,我们的想法是将IEnumerable转换为DbDataReader

 public class ObjectDataReader : DbDataReader { private bool _iteratorOwned; private IEnumerator _iterator; private IDictionary _propertyNameToOrdinal = new Dictionary(); private IDictionary _ordinalToPropertyName = new Dictionary(); private Func[] _getPropertyValueFuncs; public ObjectDataReader(IEnumerable enumerable) { if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); _iteratorOwned = true; _iterator = enumerable.GetEnumerator(); _iterator.MoveNext(); Initialize(); } public ObjectDataReader(IEnumerator 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[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)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(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 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的博客文章

我有同样的问题,你不能创建一个DataTable ,因此只是将其转储到工作表中。

Core中缺少DataTable支持会强制您创建强类型对象,然后循环并将这些对象映射到EPPlus的输出。

所以一个非常简单的例子是:

 // Get your data directly from EF, // or from whatever other source into a list, // or Enumerable of the type List 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方法在内部执行相同的操作并循环遍历每一行,我也不会感到惊讶。

您可以创建一个扩展方法来使用generics传递对象列表并使用reflection来正确映射它…也许我会看看项目,看看我是否可以贡献。

编辑添加:

在.NET Core中,从GitHub问题跟踪器看, DataTable支持在优先级列表上相当低,并且不希望它很快就会出现。 我认为这也是一个哲学观点,因为这个概念通常是你尝试使用强类型对象。 因此,过去可以将SQL查询运行到DataTable并运行…现在,您应该将该查询运行到模型中,通过DbSet直接映射到具有Entity Framework的表,或者使用ModelBinding和将类型传递给查询。

然后你有一个IQueryable ,作为DataTables强类型替换。 为了公平对待这种方法,99%的情况下它是一种有效且更好的方法……然而,总会有时候缺少DataTables会导致问题,需要解决这些问题!

进一步编辑

ADO.NET您可以将datareader转换为强类型的对象列表: 如何轻松地将DataReader转换为List ? 仅举一个例子。 使用此列表,您可以从那里进行映射。

如果您想/必须使用面向ASP.NET Core framework ASP.NET Core ,那么您必须这样做。 如果您可以使用Core项目定位Net 4.5,那么您将能够使用System.Data并重新使用DataTables – 唯一需要注意的是您必须使用Windows服务器和IIS。

你真的需要完整的.Net Core框架吗? 你需要在linux上托管吗? 如果不是,并且您确实需要DataTables,那么只需定位旧框架即可。

这个问题有两个解决方案。 一个是在他的回答中使用DbDataReader作为@meziantou建议,很高兴提供一个将IEnumerable转换为DbDataReader的generics方法。

我找到的另一个解决方案是使用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核心代码:

 var db = new SqlConnection("Server=localhost; Database=Test; User Id=test; Password=123456;"); List users = new List(); 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 values = new Dictionary(); 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(); } 

@meziantou。 我喜欢你的答案,但你的实施中存在一个突破性的错误。 第一个问题是在构造函数中调用了MoveNext,这会导致读者的任何迭代总是跳过第一个值。 一旦我删除了它,我发现为什么这样做是首先。 我更改了GetFieldType以使用Type信息,而不是从值中读取类型,这解决了这个问题。 再次,你真的很好的答案。 感谢您的发表。 这是我修复版的ObjectDataReader。

  public class ObjectDataReader : DbDataReader { private bool _iteratorOwned; private IEnumerator _iterator; private IDictionary _propertyNameToOrdinal = new Dictionary(); private IDictionary _ordinalToPropertyName = new Dictionary(); private PropertyInfoContainer[] _propertyInfos; class PropertyInfoContainer { public Func EvaluatePropertyFunction { get; set; } public Type PropertyType { get; set; } public string PropertyName { get; set; } public PropertyInfoContainer(string propertyName , Type propertyType , Func evaluatePropertyFunction) { this.PropertyName = propertyName; this.PropertyType = propertyType; this.EvaluatePropertyFunction = evaluatePropertyFunction; } } public ObjectDataReader(IEnumerable enumerable) { if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); _iteratorOwned = true; _iterator = enumerable.GetEnumerator(); //_iterator.MoveNext(); Initialize(); } public ObjectDataReader(IEnumerator 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)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(); } } 

Developer82,

我处于相同的情况,我想使用.net核心但数据库不可用,数据集是一个无赖。 因为您正在引用一个使用List的post我认为可能的目标是尽可能以最干净的方式将C#List放入数据库。 如果这是目标,那么这可能会有所帮助。

我在这里使用过Dapper的各种项目。 支持int .netcore。 下面是一个小型控制台应用程序,它接受一个填充的c#列表并将其插入到数据库中,然后在该表上发出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 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(_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 GetDataItems() { List items = new List(); 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; } } } 

我希望这段代码示例有所帮助。

谢谢。