1
\$\begingroup\$

I'm building a .NET Linq-esc query builder for my application and I'm wondering about my implementation and code structure when it comes a few classes.

Basically I'm creating a query builder for SQL, and I need for my classes to be able to using the following behavior:

query.Select(...).From(...).Join(...).Where(...);

I have created 6 Interfaces that contain one method each that donates what can be done to that class E.G the Select class would implement IFromAble which contains the method From(..). This allows the behavior above.

This is the same for all of the other classes above and a few more. Some classes, like Join will implement a few of the interfaces E.g IJoinAble, IWhereAble, IOrderAble, IGroupAble, IHavingAble.

This, as you can imagine will lead to a lot of duplicated code throughout each of my classes.

Each of the classes also implement a common base abstract class, which means I cannot use abstracts to solve the issue.

Is there another way I can implement my intended behavior without all of the duplicated code? Or is this just one of those cases I'm once again tripped up by C#'s single inheritance?

The Join Class

public class ModuleJoin : QueryModule, IJoinAble, IWhereAble, IOrderAble, IGroupAble
{
    public JoinType JoinType { get; set; }
    public QueryField JoinFrom 
    {
        get { return (QueryField)this.Fields[0]; }
        set { this.Fields[0] = value; } 
    }
    public QueryField JoinTo
    {
        get { return (QueryField)this.Fields[1]; }
        set { this.Fields[1] = value; }
    }

    public ModuleJoin(Query query, JoinType joinType, QueryField joinFrom, QueryField joinTo)
    {
        this.PerentQuery = query;
        this.Fields.Clear();
        this.Fields.Add(joinFrom);
        this.Fields.Add(joinTo);
        JoinType = joinType;
    }

    public ModuleJoin Join(JoinType joinType, QueryField joinFrom, QueryField joinTo)
    {
        ModuleJoin join = new ModuleJoin(this.PerentQuery, joinType, joinFrom, joinTo);
        this.PerentQuery.ModuleJoins.Add(join);
        return join;
    }

    public ModuleWhere Where(IFieldPart itemOne, Comparator comparator, IFieldPart itemTwo)
    {
        ModuleWhere where = new ModuleWhere(this.PerentQuery, itemOne, comparator, itemTwo);
        this.PerentQuery.ModuleWheres.Add(where);
        return where;
    }

    public ModuleGroupBy GroupBy(params IFieldPart[] fields)
    {
        ModuleGroupBy groupBy = new ModuleGroupBy(this.PerentQuery, fields);
        this.PerentQuery.ModuleGroupBy = groupBy;
        return groupBy;
    }
}

QueryModule (The interface on this just has those properties)

public abstract class QueryModule : IQueryModule
{
    public Query PerentQuery { get; set; }
    public FieldCount FieldCount { get; set; }
    public List<IFieldPart> Fields { get; set; }
    public IFieldPart Field
    {
        get { return Fields[0]; }
        set 
        {
            if (Fields.Count == 1)
                Fields[0] = value;
            else
                Fields.Add(value);
        }
    }

    public QueryModule()
    {
        this.Fields = new List<IFieldPart>();
    }
}

IJoinAble (An example of one of the interfaces)

interface IJoinAble
{
    ModuleJoin Join(JoinType joinType, QueryField joinFrom, QueryField joinTo);
}
\$\endgroup\$
4
  • \$\begingroup\$ Have a single class implement all of your interfaces. Where you need a composite interface, use another interface which just inherits from all the interfaces it composes. You'll need at the very most 1 composite interface per method, hopefully fewer \$\endgroup\$ Commented Jul 25, 2014 at 14:38
  • \$\begingroup\$ @LexWebb Normally you can flag your post for migration! But I'll verify on meta \$\endgroup\$
    – Marc-Andre
    Commented Jul 25, 2014 at 14:42
  • \$\begingroup\$ @LexWebb flagging is the way to go : meta.codereview.stackexchange.com/questions/902/… \$\endgroup\$
    – Marc-Andre
    Commented Jul 25, 2014 at 14:48
  • \$\begingroup\$ This question could be on-topic for Code Review, if you would just include your code. \$\endgroup\$ Commented Jul 25, 2014 at 15:25

1 Answer 1

2
\$\begingroup\$

Interesting question. The most simple and direct way to approach this problem (not necessarily the wisest) - implement Select(), From(), Join() etc. as extension methods on the interfaces ISelectable, IFromable, IJoinable, etc. The ability to write extension methods to interfaces is a powerful and fascinating language feature with many implications...

\$\endgroup\$
1
  • \$\begingroup\$ Perfect. I was able to create a single extension class to manage all of the methods for each interface. Thanks. \$\endgroup\$
    – Lex Webb
    Commented Jul 28, 2014 at 10:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.