What is the correct way to declare a chain of methods and prevent the same method from being used outside the scope?

Asked

Viewed 340 times

5

In my last questions I was creating some methods to automate some queries. It’s legal, but now I need to control access to methods by specifying a sequence.

When using the method Select(), i cannot have access to it again. After the Where() method I cannot have access to the methods Leftjoin() and Innerjoin(). That is until the method is finally arrived at Tolist();

My class will work by creating sql s commands in a chained way like Linq to Lambda.

Thus:

var pessoas = pessoaDAO.Select()
    .LeftJoin(x => x.Telefones, (telefones, pessoa) => telefones.PessoaId == pessoa.Id)
    .Where(x => x.Ativo)
    .OrderBy(x => x.Nome)
    .ToList();

It would be wrong to allow this:

var pessoas = pessoaDAO
    .Select(x => 
        x.Id, 
        x => x.Pessoa, 
        x => x.Ativo,
        x => x.Telefones)
    .Where(x => x.Ativo)
    .Select()
    .ToList();

That is, a Select() after a Where() and even a Select() have already been called.

To solve this question, I am trying to separate the methods by interface. So:

public interface IQueryBuilder<TModel> : ICustomQueryBuilder<TModel>
    where TModel : class
{
    ICustomQueryBuilder<TModel> Select();
    ICustomQueryBuilder<TModel> Select(params Expression<Func<TModel, object>>[] members);
}

public interface ICustomQueryBuilder<TModel> : 
    IQueryBuilderJoin<TModel>, 
    IQueryBuilderFilter<TModel>, 
    IQueryBuilderOrder<TModel>, 
    IQueryBuilderExecute<TModel>
    where TModel : class
{        
}

Iquerybuilder which will be the interface used to implement my inherited class of Icustomquerybuilder, and the methods Select() return the type Icustomquerybuilder so that then I cannot have access to the method Select() again.

Iquerybuilder inherits from Icustomquerybuilder and this from the other interfaces because I need to have a type implemented in the class to make the return.

Already, Icustomquerybuilder inherits from:

public interface IQueryBuilderJoin<TModel> : 
    IQueryBuilderFilter<TModel>, 
    IQueryBuilderOrder<TModel>, 
    IQueryBuilderExecute<TModel>
    where TModel : class
{
    IQueryBuilderJoin<TModel> LeftJoin<TMemberJoin>(
        Expression<Func<TModel, TMemberJoin>> member, 
        Expression<Func<TMemberJoin, TModel, bool>> filter);

    IQueryBuilderJoin<TModel> LeftJoin<TMemberJoin>(
        Expression<Func<TModel, IEnumerable<TMemberJoin>>> member, 
        Expression<Func<TMemberJoin, TModel, bool>> filter);

    IQueryBuilderJoin<TModel> InnerJoin<TMemberJoin>(
        Expression<Func<TModel, TMemberJoin>> member, 
        Expression<Func<TMemberJoin, TModel, bool>> filter);

    IQueryBuilderJoin<TModel> InnerJoin<TMemberJoin>(
        Expression<Func<TModel, IEnumerable<TMemberJoin>>> member, 
        Expression<Func<TMemberJoin, TModel, bool>> filter);
}

In sequence of:

public interface IQueryBuilderFilter<TModel> : IQueryBuilderOrder<TModel>
    where TModel : class
{
    IQueryBuilderFilter<TModel> Where(Expression<Func<TModel, bool>> filter);
}

public interface IQueryBuilderOrder<TModel> : IQueryBuilderExecute<TModel>
    where TModel : class
{
    IQueryBuilderOrder<TModel> OrderBy(params Expression<Func<TModel, object>>[] members);
    IQueryBuilderOrder<TModel> OrderByDesc(params Expression<Func<TModel, object>>[] members);
}

public interface IQueryBuilderExecute<TModel>
    where TModel : class
{
    List<TModel> ToList();
}

Finally, it would have:

public class QueryBuilder<TModel> : IQueryBuilder<TModel>
    where TModel : class
{
   // ... implementações
}

It is correct to do this kind of implementation?

To better illustrate the need for return, example:

public class PessoaDAO : GenericDAO<Pessoa>
{
    // ... outros métodos

    public IQueryBuilderFilter<Pessoa> Where(Expression<Func<TModel, bool>> filter)
    {
       // ... implementações
       return this;
    }

    // ... outros métodos 
}

That is, this is necessary for the chaining of the methods and the type should be a type already implemented, otherwise the automatic cast will not be accepted.

  • Interface segregation is a good OO modeling practice. Applying this to DSL construction is very interesting. Several frameworks use the technique and I even used it a few times. Depending on the complexity of the solution it is interesting to make a state diagram to illustrate the behavior. With each state representing an interface you can make it very clear through arrows which methods continue on the same interface (state) and which cause the transition to a new state with no return. You can also see in this if the code meets everything you need and is consistent.

1 answer

2


It is correct to do this kind of implementation?

From the point of view only of the objective of implementation, yes. In fact, I would say that this is the performative (elegant) way of allowing or not the chaining of methods.

However, I believe that this organization may characterize a loss of functionality. There is nothing wrong in allowing a OrderBy() after a Where(), or a Where() after a Select(). I understand that standardization serves to make the set of extension methods more like an ANSI SQL language, but the idea of extension methods is not to approach SQL: the idea is to work with agnostic set manipulation.

  • Orderby() after a Where(), or a Where() after a Select(), ok. But I presented a Select(). Where(). Select().. that you said is not cool. Thank you!

  • @user18098 Why? Not SQL.

  • It is not SQL but it is to create. Just for what I want I find not interesting.

  • Thank you! See you...

Browser other questions tagged

You are not signed in. Login or sign up in order to post.