代码之家  ›  专栏  ›  技术社区  ›  P Daddy

将LINQ移植到C#2库上

  •  2
  • P Daddy  · 技术社区  · 14 年前

    我正在写一个数据访问层。它将有C#2和C#3客户机,所以我是根据2.0框架编译的。虽然鼓励使用存储过程,但我仍在尝试提供相当完整的执行即席查询的能力。我已经做得很好了。

    为了方便C#3客户机,我尝试提供尽可能多的与LINQ查询语法的兼容性。斯基特 noticed LINQ查询表达式是duck类型的,所以我不知道 有一个 IQueryable IQueryProvider (或 IEnumerable<T> )使用它们。我只需要提供具有正确签名的方法。

    所以我得到了 Select , Where OrderBy , OrderByDescending ThenBy ,和 ThenByDescending Join GroupJoin . 我让他们工作,但只有一个加入。

    下面是一个简短的可编译示例:

    // .NET 2.0 doesn't define the Func<...> delegates, so let's define some workalikes
    delegate TResult FakeFunc<T, TResult>(T arg);
    delegate TResult FakeFunc<T1, T2, TResult>(T1 arg1, T2 arg2);
    
    abstract class Projection{
        public static Condition operator==(Projection a, Projection b){
            return new EqualsCondition(a, b);
        }
        public static Condition operator!=(Projection a, Projection b){
            throw new NotImplementedException();
        }
    }
    class ColumnProjection : Projection{
        readonly Table  table;
        readonly string columnName;
    
        public ColumnProjection(Table table, string columnName){
            this.table      = table;
            this.columnName = columnName;
        }
    }
    abstract class Condition{}
    class EqualsCondition : Condition{
        readonly Projection a;
        readonly Projection b;
    
        public EqualsCondition(Projection a, Projection b){
            this.a = a;
            this.b = b;
        }
    }
    class TableView{
        readonly Table        table;
        readonly Projection[] projections;
    
        public TableView(Table table, Projection[] projections){
            this.table       = table;
            this.projections = projections;
        }
    }
    class Table{
        public Projection this[string columnName]{
            get{return new ColumnProjection(this, columnName);}
        }
    
        public TableView Select(params Projection[] projections){
            return new TableView(this, projections);
        }
        public TableView Select(FakeFunc<Table, Projection[]> projections){
            return new TableView(this, projections(this));
        }
        public Table     Join(Table other, Condition condition){
            return new JoinedTable(this, other, condition);
        }
        public TableView Join(Table inner,
                              FakeFunc<Table, Projection> outerKeySelector,
                              FakeFunc<Table, Projection> innerKeySelector,
                              FakeFunc<Table, Table, Projection[]> resultSelector){
            Table join = new JoinedTable(this, inner,
                new EqualsCondition(outerKeySelector(this), innerKeySelector(inner)));
            return join.Select(resultSelector(this, inner));
        }
    }
    class JoinedTable : Table{
        readonly Table     left;
        readonly Table     right;
        readonly Condition condition;
    
        public JoinedTable(Table left, Table right, Condition condition){
            this.left      = left;
            this.right     = right;
            this.condition = condition;
        }
    }
    

    这使我能够在C#2中使用一种相当不错的语法:

    Table table1 = new Table();
    Table table2 = new Table();
    
    TableView result =
        table1
        .Join(table2, table1["ID"] == table2["ID"])
        .Select(table1["ID"], table2["Description"]);
    

    但在C#3中有一个更好的语法:

    TableView result =
        from t1 in table1
        join t2 in table2 on t1["ID"] equals t2["ID"]
        select new[]{t1["ID"], t2["Description"]};
    

    这很有效,给了我和第一个案例相同的结果。问题是我是否想加入第三个表。

    TableView result =
        from t1 in table1
        join t2 in table2 on t1["ID"] equals t2["ID"]
        join t3 in table3 on t1["ID"] equals t3["ID"]
        select new[]{t1["ID"], t2["Description"], t3["Foo"]};
    

    现在我得到一个错误(不能隐式地将类型'AnonymousType#1'转换为'Projection[]),可能是因为第二个联接试图将第三个表联接到包含前两个表的匿名类型。当然,这种匿名类型没有 加入 方法。

    有什么关于我怎么做的提示吗?

    2 回复  |  直到 14 年前
        1
  •  2
  •   Tomas Petricek    14 年前

    这是一个非常有趣的设计,我喜欢它! Join 方法太具体了。您的定义与LINQ中的定义之间的关键区别如下:

    public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
        /* cut */, Func<TOuter, TInner, TResult> resultSelector)
    
    public TableView Join(
         /* cut */, FakeFunc<Table, Table, Projection[]> resultSelector)
    

    当LINQ编译具有多个 join resultSelector 对于第一个,生成的代码将自动返回一个简单的匿名类型,其中包含两个源表中的元素。因此,如果我是正确的,在您的例子中,生成的匿名类型将如下所示:

    new { t1 : Projection; t2 : Projection }
    

    不幸的是,这与 Projection[] (尽管 语义上 ,差别不大)。恐怕解决这个问题的唯一方法就是使用动态类型转换和反射。

    • 你需要修改 所以它有泛型类型参数 TResult 用于 结果选择器

    • 加入 你跑的方法 TResult res = resultSelector(...) res 价值观。

    • res is Projection[] 然后可以使用现有代码(这种情况将在包含单个 条款)

    • 在另一种情况下, 是类似于上面的匿名类型。这意味着您需要使用反射来获取类型属性的值,并将它们转换为 Projection 价值观(然后做和现在一样的事情)。

    我没有试着实现这个,但我认为它可能会起作用。。。

        2
  •  0
  •   Community Egal    7 年前

    下面有点长。如果您只想让它工作,而不关心为什么或如何工作,那么请跳到最后两个代码部分。


    托马斯·佩特里切克的 answer TResult 属于 resultSelector

    让我们看看我之前的问题:

    TableView result =
        from t1 in table1
        join t2 in table2 on t1["ID"] equals t2["ID"]
        join t3 in table3 on t1["ID"] equals t3["ID"]
        select new[]{t1["ID"], t2["Description"], t3["Foo"]};
    

    这可以解释为:

    var intermediate =
        table1.Join(
            table2, t1=>t1["ID"], t2=>t2["ID"],
            (t1, t2)=>new{t1=t1, t2=t2}
        );
    TableView result =
        intermediate.Join(
            table3, anon=>anon.t1["ID"], t3=>t3["ID"],
            (anon, t3)=>new[]{anon.t1["ID"], anon.t2["ID"], t3["Foo"]}
        );
    

    TableView result =
        from t1 in table1
        join t2 in table2 on t1["ID"] equals t2["ID"]
        join t3 in table3 on t1["ID"] equals t3["ID"]
        join t4 in table4 on t1["ID"] equals t4["ID"]
        select new[]{t1["ID"], t2["Description"], t3["Foo"], t4["Bar"]};
    

    var intermediate1 =
        table1.Join(
            table2, t1=>t1["ID"], t2=>t2["ID"],
            (t1, t2)=>new{t1=t1, t2=t2}
        );
    var intermediate2 =
        intermediate1.Join(
            table3, anon1=>anon1.t1["ID"], t3=>t3["ID"],
            (anon1, t3)=>new{anon1=anon1, t3=t3}
        );                 
    TableView result =
        intermediate2.Join(
            table4, anon2=>anon2.anon1.t1["ID"], t4=>t4["ID"],
            (anon2, t3)=>new[]{
                anon2.anon1.t1["ID"], anon2.anon1.t2["ID"],
                anon2.t3["Foo"], t4["Bar"]
            }
        );
    

    所以 结果选择器

    很明显,我需要的不是一个,而是两个 Join 方法。我在最后一个连接中工作得很好,我需要为中间连接添加另一个连接。记住,我已有的方法返回一个 TableView :

    public TableView Join(Table inner,
                          FakeFunc<Table, Projection> outerKeySelector,
                          FakeFunc<Table, Projection> innerKeySelector,
                          FakeFunc<Table, Table, Projection[]> resultSelector){
        Table join = new JoinedTable(this, inner,
            new EqualsCondition(outerKeySelector(this), innerKeySelector(inner)));
        return join.Select(resultSelector(this, inner));
    }
    

    现在我需要添加一个 加入 方法,以便可以在链中调用:

    public Table Join<T>(Table inner,
                         FakeFunc<Table, Projection> otherKeySelector,
                         FakeFunc<Table, Projection> innerKeySelector,
                         FakeFunc<Table, Table, T> resultSelector){
        Table join = new JoinedTable(this, inner,
            new EqualsCondition(outerKeySelector(this), innerKeySelector(inner)));
        // calling resultSelector(this, inner) would give me the anonymous type,
        // but what would I do with it?
        return join;
    }
    

    添加此方法使我的连接几乎可以工作。我终于可以连接三个或更多的表,但我丢失了别名:

    TableView result =
        from t1 in table1
        join t2 in table2 on t1["ID"] equals t2["ID"]
        join t3 in table3 on t1["ID"] equals t3["ID"]
                           // ^  error, 't1' isn't a member of 'Table'
        select new[]{t1["ID"], t2["Description"], t3["Foo"]};
                   // ^         ^  error, 't1' & 't2' aren't members of 'Table'
    

    TableView result =
        from t1 in table1
        join t2 in table2 on t1["ID"] equals t2["ID"]
        join t3 in table3 on table1["ID"] equals t3["ID"]
        select new[]{table1["ID"], table2["Description"], t3["Foo"]};
    

    这将编译、运行并产生预期的结果。呜呼!算是成功吧。不过,丢失别名并不理想。在实际查询中,表可能更复杂:

    TableView result =
        from t1 in table1
        join t2 in (
            from t in table2
            where t["Amount"] > 20
            select new[]{t["ID"], t["Description"]
        ).AsSubQuery() on t1["ID"] equals t2["ID"]
        join t3 in table3 on t1["ID"] equals t3["ID"]
        select new[]{table1["ID"], t2["Description"], t3["Foo"]};
                                 // ^ error, 't2' isn't a member of 'Table'
    

    在这里,我不能没有这个别名 t2

    在多次看到“'t1'不是'Table'的成员”消息后,我终于意识到秘密就在 outerKeySelector 参数到 . LINQ只是在找一个叫做 t1 (或其他)那是这个lambda的论点的一部分。我的 外部选择器

    FakeFunc<Table, Projection> outerKeySelector
    

    这个 Table t1级 dynamic 要做到这一点,但如果我使用的是C#4,那么它的整个设计就会有所不同(我确实计划稍后在C#4中重做这一点,仅针对.NET 4.0客户端,充分利用动态类型提供列投影作为表的实际属性)。不过,在.NET2.0中,我没有动态类型。那么,如何才能创建一个将表别名作为属性的类型呢?

    多好的一分钟。别挂电话。这个 已经还给我一个了!不知何故,我需要抓住这个物体并把它传给 在下一个连接中。但是怎么做呢?我不能把它储存在我的房间里 JoinedTable

    接合表 描述实际联接的实例,以及对包含别名的匿名类型的引用。尤里卡!

    最后,代码的完整功能版本添加了以下类:

    class IntermediateJoin<T>{
        readonly JoinedTable table;
        readonly T           aliases;
    
        public IntermediateJoin(JoinedTable table, T aliases){
            this.table   = table;
            this.aliases = aliases;
        }
    
        public TableView Join(Table inner,
                              FakeFunc<T, Projection> outerKeySelector,
                              FakeFunc<Table, Projection> innerKeySelector,
                              FakeFunc<T, Table, Projection[]> resultSelector){
            var join = new JoinedTable(table, inner,
                new EqualsCondition(outerKeySelector(aliases), innerKeySelector(inner)));
            return join.Select(resultSelector(aliases, inner));
        }
        public IntermediateJoin<U> Join<U>(Table inner,
                                           FakeFunc<T, Projection> outerKeySelector,
                                           FakeFunc<Table, Projection> innerKeySelector,
                                           FakeFunc<T, Table, U> resultSelector){
            var join = new JoinedTable(table, inner,
                new EqualsCondition(outerKeySelector(aliases), innerKeySelector(inner)));
            var newAliases = resultSelector(aliases, inner);
            return new IntermediateJoin<U>(join, newAliases);
        }
    }
    

    这种方法

    public IntermediateJoin<T> Join<T>(Table inner,
                          FakeFunc<Table, Projection> outerKeySelector,
                          FakeFunc<Table, Projection> innerKeySelector,
                          FakeFunc<Table, Table, T> resultSelector){
        var join = new JoinedTable(this, inner,
            new EqualsCondition(outerKeySelector(this), innerKeySelector(inner)));
        var x = resultSelector(this, inner);
        return new IntermediateJoin<T>(join, x);
    }
    

    这提供了功能齐全的连接语法!

    再次感谢托马斯·佩特里切克花时间阅读和理解我的问题,并给我一个深思熟虑的答案。

    GroupJoin SelectMany