在EF6扩展DbContext类中在运行时创建JOIN

时间:2015-05-10 04:47:34

标签: c# linq entity-framework expression dbcontext

我需要在运行时使用给定配置创建表连接。

在这种情况下,我有IQueryable属性,它是root,我需要动态创建连接。

这是我尝试过的。

public class MyDbContext : DbContext
{
    public IQueryable<T> AsDynamicQueryable<T>() where T : class
    {
        var predicate = default(Func<T, bool>); // This is a Dynamically generated predicate 
        var query = this.Set<T>().Where(predicate).AsQueryable();


        // Now here I need to append a JOIN to the above 'query'
        // So far, this is what I have done.

        var rootType = typeof(T);
        var innerType = Type.GetType("This type takes from the configuration");
        var innerExpression = this.Set(innerType).AsQueryable();

        var paramOne = Expression.Parameter(rootType, "p1");
        var paramTwo = Expression.Parameter(innerType, "p2");

        var outerKeySelector = Expression.Property(paramOne, "property_one"); //'property_one' is a property of a first parameter which takes from the configuration
        var outerKeySelectorExpression = Expression.Lambda(outerKeySelector, paramOne); // (p1)=>p1.property_one

        var innerKeySelector = Expression.Property(paramTwo, "property_two"); //'property_two' is a property of a 2nd parameter which takes from the configuration
        var innerKeySelectorExpression = Expression.Lambda(innerKeySelector, paramTwo); // (p2)=>p2.property_two

        var resultSelector = Expression.Lambda(paramOne, paramOne, paramTwo); // (p1,p2)=>p1

        var joinMethod = typeof(Queryable)
                            .GetMethods()
                            .First(m => m.Name == "Join" && m.GetParameters().Length == 5)
                            .MakeGenericMethod(rootType, innerType, typeof(int), rootType);



        // 1st Apptempt. 
        // I'm not sure that I can execute the JOIN method like this.
        // But anyway, this gives the below error when I try to execute via taking Count();
        // "This method supports the LINQ to Entities infrastructure and is not intended to be used directly from your code."
        var newQuery = (IQueryable<T>)joinMethod
                                        .Invoke(
                                            query,
                                            new object[]
                                                { 
                                                    query,
                                                    innerExpression,
                                                    outerKeySelectorExpression,
                                                    innerKeySelectorExpression,
                                                    resultSelector
                                                });

        var tt = newQuery.Count(); // Here I just try to execute the expression to check whether it works before I return the Queryable.


        // 2nd Attempt
        // This also gives the following error when I try to execute via taking Count();
        // Unable to create a constant value of type '<type name of the root(T) type>'. Only primitive types or enumeration types are supported in this context.
        var joinMethodCallExpression = Expression.Call(
                                                    null,
                                                    joinMethod,
                                                    query.Expression,
                                                    innerExpression.Expression,
                                                    outerKeySelectorExpression,
                                                    innerKeySelectorExpression,
                                                    resultSelector);

        var xx = this.Set<T>().AsQueryable().Provider.CreateQuery<T>(joinMethodCallExpression);

        var te = xx.Count(); // Here I just try to execute the expression to check whether it works before I return the Queryable.

        throw new NotImplementedException();
    }
}

非常感谢有人能指出正确的方法。

1 个答案:

答案 0 :(得分:0)

这是代码。我在代码中添加了我的评论:

public IQueryable<T> AsDynamicQueryable<T>() where T : class
{
    // ERROR!!! It should be Expression<Func<T, bool>>
    // GetPredicate<T>() is my method to get the predicate. You must
    // put here yours. IT must return an Expression<Func<T, bool>>
    Expression<Func<T, bool>> predicate = GetPredicate<T>(); // This is a Dynamically generated predicate 

    // ERROR!!! Don't EVER use AsQueryable(), unless you exactly know
    // what you are doing. In this example, your use of AsQueryable<>()
    // is hiding the fact that you are executing the Where() LOCALLY,
    // because it is a Where(this IEnumerable<>, Func<>) instead of
    // being a Where(this IQueryable<>, Expression<>)
    // If you want an IQueryable<>, put it in a IQueryable<> variable
    IQueryable<T> query = this.Set<T>().Where(predicate);

    var rootType = typeof(T);
    var innerType = GetAsDynamicQueryableInnerType<T>();

    // Same as before! Don't use .AsQueryable(). In this case, use 
    // IQueryable (non-generic). Note that in this case there was
    // no problem with yoru code, so AsQueryable() wasn't doing 
    // "damage"
    IQueryable innerExpression = this.Set(innerType);

    var paramOne = Expression.Parameter(rootType, "p1");
    var paramTwo = Expression.Parameter(innerType, "p2");

    // GetPrimaryKey() is my method to get the property to use.
    // it returns a string with the name of the property
    string primaryKeyRootType = GetPrimaryKey(rootType);
    var outerKeySelector = Expression.Property(paramOne, primaryKeyRootType); //'property_one' is a property of a first parameter which takes from the configuration
    var outerKeySelectorExpression = Expression.Lambda(outerKeySelector, paramOne); // (p1)=>p1.property_one

    // GetForeignKey() is my method to get the property to use.
    // it returns a string with the name of the property
    var foreignKeyInnerType = GetForeignKey(innerType, rootType);
    var innerKeySelector = Expression.Property(paramTwo, foreignKeyInnerType); //'property_two' is a property of a 2nd parameter which takes from the configuration
    var innerKeySelectorExpression = Expression.Lambda(innerKeySelector, paramTwo); // (p2)=>p2.property_two

    var resultSelector = Expression.Lambda(paramOne, paramOne, paramTwo); // (p1,p2)=>p1

    // Using outerKeySelector.Type as the type of the third parameter
    // here. 99% it is typeof(int), but why not make it more generic?
    var joinMethod = typeof(Queryable)
                        .GetMethods()
                        .First(m => m.Name == "Join" && m.GetParameters().Length == 5)
                        .MakeGenericMethod(rootType, innerType, outerKeySelector.Type, rootType);


    // Queryable.Join is static, so the first parameter must be null!
    // Then the parameters to pass to Queryable.Join are the ones you
    // where using in the 1st case.
    var newQuery = (IQueryable<T>)joinMethod.Invoke(
                                        null,
                                        new object[]
                                        { 
                                            query,
                                            innerExpression,
                                            outerKeySelectorExpression,
                                            innerKeySelectorExpression,
                                            resultSelector
                                        });

    return newQuery;
}

然后存在一个大问题:即使它可以工作,你也只能获得IQueryable<T>,但加入的结果通常是IQueryable<T+U>。我看到你写了resultSelector = ... (p1,p2)=>p1,但它真的是你想要的吗?