How to dynamically generate OR comparisons with LINQ?

Asked

Viewed 106 times

3

I have a query where I should find certain values comparing whether a column is equal to a value X.

X is an array that can have 1 to 4 values.

How can I in mine query with LINQ dynamically assemble these OR?

Below follows as is my code:

public List<Meta> ListaMetasDeCategorias(List<CategoriaMetaOrgao> ListaCategoriaMetaOrgao, int orgaoId, int temporadaId)
{
    List<int> arrayIdCategorias = new List<int>();
    foreach(CategoriaMetaOrgao categoriaMetaOrgao in ListaCategoriaMetaOrgao)
        arrayIdCategorias.Add(categoriaMetaOrgao.CategoriaMeta.categoriaMetaId);

    var q = from a in Repository.Context.Meta
            where a.Temporada.temporadaId == temporadaId
            && a.Orgao.orgaoId == orgaoId
            && (a.CategoriaMeta.categoriaMetaId == arrayIdCategorias[0] ||
            a.CategoriaMeta.categoriaMetaId == arrayIdCategorias[1] ||
            a.CategoriaMeta.categoriaMetaId == arrayIdCategorias[2] ||
            a.CategoriaMeta.categoriaMetaId == arrayIdCategorias[3])
            select a;

    return q.ToList();
}

In the section below, how can I make the comparison dynamically? Because the array will not always have the 4 values.

a.CategoriaMeta.categoriaMetaId == arrayIdCategorias[0] ||
a.CategoriaMeta.categoriaMetaId == arrayIdCategorias[1] ||
a.CategoriaMeta.categoriaMetaId == arrayIdCategorias[2] ||
a.CategoriaMeta.categoriaMetaId == arrayIdCategorias[3]
  • What version of EF and . Net Framework are you using?

  • @Jefersonalmeida EF 3.5

2 answers

4


How you use version 1 of Entity Framework, also known as version 3.5, I see 2 possibilities to solve the problem.

1. One of them is to create an extension method for this.

public static IQueryable<TEntity> WhereIn<TEntity, TValue>
(
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    IEnumerable<TValue> collection
)
{
    if (selector == null) throw new ArgumentNullException("selector");
    if (collection == null) throw new ArgumentNullException("collection");
    if (!collection.Any())
        return query.Where(t => false);

    ParameterExpression p = selector.Parameters.Single();

    IEnumerable<Expression> equals = collection.Select(value =>
       (Expression)Expression.Equal(selector.Body,
            Expression.Constant(value, typeof(TValue))));

    Expression body = equals.Aggregate((accumulate, equal) =>
        Expression.Or(accumulate, equal));

    return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}

To use it:

public List<Meta> ListaMetasDeCategorias(List<CategoriaMetaOrgao> ListaCategoriaMetaOrgao, int orgaoId, int temporadaId)
{
    List<int> arrayIdCategorias = new List<int>();
    foreach(CategoriaMetaOrgao categoriaMetaOrgao in ListaCategoriaMetaOrgao)
        arrayIdCategorias.Add(categoriaMetaOrgao.CategoriaMeta.categoriaMetaId);

    var q = from a in Repository.Context.Meta
            where a.Temporada.temporadaId == temporadaId
            && a.Orgao.orgaoId == orgaoId
            select a;

    q.WhereIn(a => a.CategoriaMeta.categoriaMetaId, arrayIdCategorias);

    return q.ToList();
}

2. The other solution and that I do not recommend much is to transform your query to a list and then filter the Contains, the problem this way is that you are not making the filter in select, thus perhaps making your query heavier.

public List<Meta> ListaMetasDeCategorias(List<CategoriaMetaOrgao> ListaCategoriaMetaOrgao, int orgaoId, int temporadaId)
{
    List<int> arrayIdCategorias = new List<int>();
    foreach(CategoriaMetaOrgao categoriaMetaOrgao in ListaCategoriaMetaOrgao)
        arrayIdCategorias.Add(categoriaMetaOrgao.CategoriaMeta.categoriaMetaId);

    var q = from a in Repository.Context.Meta
            where a.Temporada.temporadaId == temporadaId
            && a.Orgao.orgaoId == orgaoId
            select a;

    var y = q.ToList().Where( a => arrayIdCategorias.Contains(a.CategoriaMeta.categoriaMetaId));


    return y;
}

Obs: If you used the Entity Framework 4 or higher, you could use the Contains, it picks up a list and checks if an element is in that list.

public List<Meta> ListaMetasDeCategorias(List<CategoriaMetaOrgao> ListaCategoriaMetaOrgao, int orgaoId, int temporadaId)
{
    List<int> arrayIdCategorias = new List<int>();
    foreach(CategoriaMetaOrgao categoriaMetaOrgao in ListaCategoriaMetaOrgao)
        arrayIdCategorias.Add(categoriaMetaOrgao.CategoriaMeta.categoriaMetaId);

    var q = from a in Repository.Context.Meta
            where a.Temporada.temporadaId == temporadaId
            && a.Orgao.orgaoId == orgaoId
            && arrayIdCategorias.Contains(a.CategoriaMeta.categoriaMetaId)
            select a;

    return q.ToList();
}
  • It didn’t work out. Error given: "LINQ to Entities does not recognize the 'Boolean Contains(Int32)' method, which cannot be converted to a storage expression."

  • @Georgewurthmann what version of his Entity Framework?

  • @Georgewurthmann EF 3.5 does not support Contains, I will come up with another answer for you

  • Cool, I tried the second suggestion and it worked. But why do you think the query could get heavier with this option? After I made the query I brought the list I will not do another query in the database, I will simply query the list generated by the query. That?

  • 1

    @Georgewurthmann the Ideal is always make the filters before returning the list, so that n bring unnecessary data, depending on the filter this can make a difference of many data and with this the query will get heavier, does not mean that it will happen this, but it’s a possibility. Note that in your problem I made all the filters that could before to then do the other, so it will already bring a reduced amount of data optimizing your query.

  • There is no Entity Framework 3.5. Please adjust the answer to the older version of the Entity Framework.

  • 1

    @Ciganomorrisonmendez does exist, the first version of EF is known as 3.5 https://msdn.microsoft.com/en-us/library/jj574253(v=vs.113). aspx

  • So, this name "EF 3.5" is a big forced bar. It is the first version, but it should be called "EF on . NET 3.5". In Soen, they only call EF v1, precisely because it is the first one. I think I will recreate the tag as a synonym to avoid these things.

  • @Ciganomorrisonmendez but MS’s own website calls it 3.5 tb, in fact it is known as EF or EF 3.5, I do not know the reason why

  • Just what I told you. The first EF is on top of . NET 3.5, and it’s a terror. That name only disturbs, in fact.

Show 5 more comments

1

To List call for arrayIdCategorias has the method Any where it will sweep the array seeking to satisfy the condition.

In the case below, it sweeps the arrayIdCategorias looking for some value int equal to a.CategoriaMeta.categoriaMetaId.

In case he finds the Any returns true, if not false.

public List<Meta> ListaMetasDeCategorias(List<CategoriaMetaOrgao> ListaCategoriaMetaOrgao, int orgaoId, int temporadaId)
{
    List<int> arrayIdCategorias = new List<int>();
    foreach(CategoriaMetaOrgao categoriaMetaOrgao in ListaCategoriaMetaOrgao)
        arrayIdCategorias.Add(categoriaMetaOrgao.CategoriaMeta.categoriaMetaId);

    var q = from a in Repository.Context.Meta
        where a.Temporada.temporadaId == temporadaId
        && a.Orgao.orgaoId == orgaoId
        && arrayIdCategorias.Any(b=>b == a.CategoriaMeta.categoriaMetaId)
        select a;

    return q.ToList();
}
  • With any it worked, but the q.Tolist that gave error now: "Could not create a constant value of type 'Lock type'. Support is only for primitive types ('Int32, String or Guid') in this context."

  • I believe the error you made in the return is due to version 3.5 of my EF. But surely the answer will help other users with higher versions.

Browser other questions tagged

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