为什么/如何使用泛型进行动态处理?

时间:2016-01-27 01:41:01

标签: c# entity-framework generics

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;我可以回到我的第二个实现,因为它仍然非常紧凑。然而,它让我想知道,发生了什么?

2 个答案:

答案 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;调用您的测试方法实例

我无法测试但是应该可以使用