tl; dr dynamic
如何处理采用IQueryable<T>
泛型的方法?那为什么尝试直接使用IQueryable<T>
dynamic
实例可用的方法呢?
为了快速了解背景,我正在开发一个测试项目,以针对数据库检查我们的Entity Framework上下文,以便在我们移动环境时做好更充分的准备。我最初想出了这个简单的测试,看看实体是否会根据现有模式加载:
private static bool CheckSchema<T>(IQueryable<T> dbSet)
{
try
{
dbSet.FirstOrDefault();
return true;
}
catch
{
return false;
}
}
private async void TestSchemaButton_Click(object sender, RoutedEventArgs e)
{
LayoutRoot.IsEnabled = false;
ResultsPane.Text = "";
var cStrings = LoadEnvironmentConnectionStrings();
var results = new StringBuilder();
ResultsPane.Text += "--- Testing " + nameof(MyProject.MyDbContext1) + "...";
await Task.Run(() =>
{
using (var db = new MyProject.MyDbContext1())
{
db.Database.Connection.ConnectionString = cStrings.Item1;
if (!CheckSchema(db.MyDbSetProperty1)) results.AppendLine(nameof(MyProject.MyDbContext1.MyDbSetProperty1) + " has invalid schema.");
// ...
if (!CheckSchema(db.MyDbSetPropertyN)) results.AppendLine(nameof(MyProject.MyDbContext1.MyDbSetPropertyN) + " has invalid schema.");
}
});
ResultsPane.Text += Environment.NewLine + results.ToString() +
"--- End " + nameof(MyProject.MyDbContext1) + Environment.NewLine + Environment.NewLine;
}
这很简单,完成了我的目标。但是,我有多个DB上下文要测试,一些有很多很多实体。我是一名程序员,所以我当然很懒,虽然它主要是复制粘贴,但仍有很多重命名要做。由于性能不是这个手动运行的测试项目的关注点,我想我可以让所有实体通过反射进行测试。
在这一点上,我开始陷入困境,因为我需要我的属性为IQueryable<T>
,但我不知道在涉及泛型时如何施放。我意识到我可以使用dynamic
作弊,因此我的Task
身体可以成为:
using (var db = new MyProject.MyDbContext1())
{
db.Database.Connection.ConnectionString = connectionString;
var dbSetProps = db.GetType().GetProperties().Where(pi => pi.PropertyType.Name == "DbSet`1");
foreach (var dbSetProp in dbSetProps)
{
dynamic dbSetVal = dbSetProp.GetValue(db);
if (!CheckSchema(dbSetVal))
results.AppendLine(dbSetProp.Name + " has invalid schema.");
}
}
我惊喜地发现这简直有效。我不知道的是怎么样?我认为即使像泛型一样通用,它们仍然是编译时构造。
此外,我认为我可以更改整个CheckSchema
方法来执行Task
正文中的所有工作,因此我更新了如下:
private static string CheckSchema<T>(string connectionString) where T : DbContext, new()
{
var invalidSchemas = new StringBuilder();
using (var db = new T())
{
db.Database.Connection.ConnectionString = connectionString;
var dbSetProps = db.GetType().GetProperties().Where(pi => pi.PropertyType.Name == "DbSet`1");
foreach (var dbSetProp in dbSetProps)
{
dynamic dbSetVal = dbSetProp.GetValue(db);
try
{
dbSetVal.FirstOrDefault();
}
catch
{
invalidSchemas.AppendLine(dbSetProp.Name + " has invalid schema.");
}
}
}
return invalidSchemas.ToString();
}
但现在它始终遇到dbSetVal.FirstOrDefault()
处的异常,说&#34; DbSet<MyProject.MyDbContext1.MyDbSetPropertyNEntityType>
不包含方法FirstOrDefault
。&#34;我可以回到我的第二个实现,因为它仍然非常紧凑。然而,它让我想知道,发生了什么?
答案 0 :(得分:3)
dynamic
实质上告诉编译器绕过静态检查。这意味着所有绑定必须在运行时完成,而不是在编译时完成。
IQueryable<Person> people = GetPeople();
dynamic dPerson = people;
此处,dPerson
仍 IQueryable<Person>
,但我们特别要求编译器不要创建任何关于它的编译时假设。如果要写dPerson.ToString()
,方法ToString
只会被解析,因为它被称为。
这样做的结果是,你失去了扩展方法的语法糖。写下以下内容:
people.FirstOrDefault()
实际撰写Queryable.FirstOrDefault(people);
您无法在dynamic
个对象上使用扩展方法。在运行时,它将在对象本身上查找方法FirstOrDefault
,并且不会检查可能的扩展方法。
您需要编写Queryable.FirstOrDefault(dbSetVal);
答案 1 :(得分:0)
我建议忘记动态并使用反射。您需要通过反射为每个实体类型调用dbContext.Set<xx>()
,因此假设您在变量“entityType”中有实体类型:
//this should be done outside the loop of entity types
var setMethodInfo = db.GetType().GetMethod("Set");
//build the Set method info for the current type
var setEntityTypeMI = setMethodInfo.MakeGenericMethod(entityType);
//Execute
var dbSetToTest = setEntityTypeMI.Invoke(db, null);
现在,您可以使用DbSet&lt;&gt;调用您的测试方法实例
我无法测试但是应该可以使用