我的任务是创建一个 Blazor ASP.NET Core 应用程序,它将每个表从 MS SQL Server 数据库导出到它自己的
.csv
文件中。我已将 DbSets
属性中的 DbContext
提取到通用列表中,但是当我尝试将通用对象转换为它们的 DbSet
类时,出现以下错误:
处理请求时发生未处理的异常。 MissingMethodException:类型的构造函数 'Microsoft.EntityFrameworkCore.DbSet`1[[DatabaseTableExport.Data.LoginDbModels.AccountPasswordHistory, DatabaseTableExport,版本=1.0.0.0,文化=中性, PublicKeyToken=null]]' 未找到。 System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder 活页夹,对象[]参数,CultureInfo文化)
如何修复此错误,或者是否有更好的方法从
DbSets
中提取 DbContext
?
using DatabaseTableExport.Data;
using DatabaseTableExport.Data.LoginDbModels;
using Microsoft.AspNetCore.Components;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace DatabaseTableExport.Pages
{
public partial class Index : ComponentBase
{
[Inject]
private LoginDbContext LoginDbContext { get; set; }
[Inject]
private ApplicationDbContext ApplicationDbContext { get; set; }
protected override void OnInitialized()
{
List<DbSet<object>> dbSets = GetDbSets(LoginDbContext);
// iterate through each DbSet instance
foreach (var dbSet in dbSets)
{
// export to csv here
}
}
private static List<DbSet<object>> GetDbSets(DbContext loginDbContext)
{
// Retrieve all the DbSet properties declared on the context class.
var dbSetProperties = loginDbContext.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
.ToList();
// Iterate over each DbSet property and add it to the list of dbsets.
var dbSets = new List<DbSet<object>>();
foreach (var property in dbSetProperties)
{
// If the type of the DbSet is null, skip it.
if (property.PropertyType.GenericTypeArguments.Length <= 0)
{
continue;
}
// Get the generic type arguments and create a corresponding DbSet instance.
var dbsetGenericType = property.PropertyType.GenericTypeArguments[0];
var convertedDbSet = Activator.CreateInstance(typeof(DbSet<>).MakeGenericType(dbsetGenericType), property.GetValue(loginDbContext));
dbSets.Add((DbSet<object>)convertedDbSet);
}
// Return the list of DbSet objects.
return dbSets;
}
}
}
我建议采用不同的方法。 EF Core 无需
DbContext
属性即可工作。可以从 DbContext 的 Model 中检索元数据信息,并可以通过 CallBack 启动导出。
回调接口:
public interface IRecordsetProcessor
{
void ProcessRecordset<T>(DbContext context, IQueryable<T> recordset, IEntityType entityType)
where T: class;
}
记录集枚举实现:
public static class DbContextUtils
{
public static void ProcessRecordset<T>(DbContext context, IEntityType entityType, IRecordsetProcessor processor)
where T : class
{
processor.ProcessRecordset(context, context.Set<T>(), entityType);
}
private static MethodInfo _processMethod = typeof(DbContextUtils).GetMethod(nameof(ProcessRecordset))!;
public static void ProcessRecordsets(DbContext context, IRecordsetProcessor processor)
{
var entityTypes = context.Model.GetEntityTypes()
.Where(et => !et.IsOwned())
.ToList();
foreach (var et in entityTypes)
{
_processMethod.MakeGenericMethod(et.ClrType).Invoke(null, new object[] { context, et, processor });
}
}
}
导出的简单示例实现:
public class ExportProcessor : IRecordsetProcessor
{
private const string ColumnSeparator = ",";
static string PreparePropertyValue(IProperty prop, object record)
{
var clrValue = prop.GetGetter().GetClrValue(record);
// without converter
var value = clrValue;
// with converter
/*
var converter = prop.GetValueConverter();
var value = converter == null ? clrValue : converter.ConvertToProvider(clrValue);
*/
if (value == null)
return "";
var strValue = value.ToString() ?? "";
if (strValue.Contains(ColumnSeparator) || strValue.Contains('\"'))
{
strValue = "\"" + strValue.Replace("\"", "\"\"") + "\"";
}
return strValue;
}
public void ProcessRecordset<T>(DbContext context, IQueryable<T> recordset, IEntityType entityType) where T : class
{
var exportItems = new List<string>();
var properties = entityType.GetProperties().ToList();
// produce header
exportItems.Add(string.Join(ColumnSeparator, properties.Select(p => p.GetColumnName())));
foreach (var record in recordset.AsNoTracking())
{
exportItems.Add(string.Join(ColumnSeparator, properties.Select(p => PreparePropertyValue(p, record))));
}
// TODO: save to file, or do that without intermediate list
}
}
使用示例:
DbContextUtils.ProcessRecordsets(context, new ExportProcessor());