Grafting LINQ onto C# 2 library

Posted by P Daddy on Stack Overflow See other posts from Stack Overflow or by P Daddy
Published on 2010-04-07T01:51:14Z Indexed on 2010/04/07 1:53 UTC
Read the original article Hit count: 375

Filed under:
|
|

I'm writing a data access layer. It will have C# 2 and C# 3 clients, so I'm compiling against the 2.0 framework. Although encouraging the use of stored procedures, I'm still trying to provide a fairly complete ability to perform ad-hoc queries. I have this working fairly well, already.

For the convenience of C# 3 clients, I'm trying to provide as much compatibility with LINQ query syntax as I can. Jon Skeet noticed that LINQ query expressions are duck typed, so I don't have to have an IQueryable and IQueryProvider (or IEnumerable<T>) to use them. I just have to provide methods with the correct signatures.

So I got Select, Where, OrderBy, OrderByDescending, ThenBy, and ThenByDescending working. Where I need help are with Join and GroupJoin. I've got them working, but only for one join.

A brief compilable example of what I have is this:

// .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;
    }
}

This allows me to use a fairly decent syntax in C# 2:

Table table1 = new Table();
Table table2 = new Table();

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

But an even nicer syntax in C# 3:

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

This works well and gives me identical results to the first case. The problem is if I want to join in a third table.

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"]};

Now I get an error (Cannot implicitly convert type 'AnonymousType#1' to 'Projection[]'), presumably because the second join is trying to join the third table to an anonymous type containing the first two tables. This anonymous type, of course, doesn't have a Join method.

Any hints on how I can do this?

© Stack Overflow or respective owner

Related posts about LINQ

Related posts about c#