我在SQL Server中有一个存储过程,它接受用户定义的表类型。我正在关注如何在SQL中将DataTable发送到存储过程的帖子Bulk insert from C# list into SQL Server into multiple tables with foreign key constaints中的答案。
但是当我创建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?
答案 0 :(得分:12)
.NET CORE 2.0现在支持DataTable。请参阅我在.Net Core how to implement SQLAdapter ./ DataTable function的回答。以下示例代码适用于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;
}
答案 1 :(得分:8)
您可以使用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
答案 2 :(得分:1)
我遇到了同样的问题,您无法创建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
支持在优先级列表上相当低,并且不会很快就会发现它。我认为这也是一个哲学观点,因为这个概念通常是你尝试使用强类型对象。因此,过去可以将SQL查询运行到DataTable
并运行...现在,您应该将该查询运行到模型中,直接映射到具有Entity Framework
的表DbSet
或ModelBinding
并将类型传递给查询。
然后,您有IQueryable<T>
作为DataTables
的强类型替代品。为了公平对待这种方法,对99%的情况来说,它是一种有效且更好的方法......但总有时候DataTables
的缺失会导致问题,需要解决这些问题!
进一步修改
在ADO.NET
中,您可以将datareader
转换为强类型的对象列表:
How can I easily convert DataReader to List<T>?仅举一例。使用此列表,您可以从那里进行映射。
如果您想/必须使用定位ASP.NET Core
的{{1}},那么您必须这样做。如果您可以使用Core项目定位Net 4.5,那么您将能够使用ASP.NET Core framework
并让System.Data
返回 - 唯一需要注意的是您必须使用Windows服务器和IIS。
您真的需要完整的DataTables
框架吗?你需要在linux上托管吗?如果不是,并且您确实需要DataTables,那么只需定位旧框架。
答案 3 :(得分:1)
这个问题有两个解决方案。一个是使用DbDataReader
作为@meziantou在他的回答中建议的,很高兴提供一个将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核心代码:
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();
}
答案 4 :(得分:1)
@meziantou。我喜欢你的答案,但你的实施中存在一个突破性的错误。第一个问题是在构造函数中调用了MoveNext,这会导致读者的任何迭代总是跳过第一个值。一旦我删除了它,我就发现了为什么这样做是在第一时间完成的。我更改了GetFieldType以使用Type信息,而不是从值中读取类型,这解决了这个问题。再次,你真的很好的答案。感谢您的发表。这是我修复版的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();
}
}
答案 5 :(得分:0)
Developer82,
我处于相同的情况,我想使用.net核心,但数据库不可用,数据集是一个无赖。因为你正在引用一个使用List的帖子我认为可能的目标是尽可能以最干净的方式将C#List放入数据库。如果这是目标,那么这可能会有所帮助。
我在各个项目中使用了位于here的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<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;
}
}
}
我希望此代码示例有所帮助。
谢谢。