简体   繁体   中英

How to Dynamically build Select as relates to Linq & Entity Framework

I would like to do the following

public async Task<List<dynamic>> searchEntities(string query, list<string> columnNames)
{
   var columNames = GetPropertiesUsingReflectionAndfilter<Entity>(columnNames);
   await db.entities.select(x => { columnNames }).where(...)..ToListAsync();
}

I need to expose a custom EF Search Method in a Controller. I would like to offer a partial response. $search is not avail in version of OData I am using.

Is this possible with IQueryable perhaps?

Alternative would be to build Parameterized SQL query. Would like to know if there is a better way using EF and Linq? Thanks..

Solution:

Thanks to @IvanStoev, I ultimately added a library, System.Linq.Dynamic (free with > 1 million downloads) , to accomplish this. I now have.

using System.Linq.Dynamic;

            var columnNames = GetColumNames<Entity>(qry.TableList);
            var selector = "new(" + String.Join(", ", columnNames.ToArray()) + ")";

            var results = await db.Lessons
                .Where(x => x.LessonName.Contains(qry.Query) == true || x.HTML.Contains(qry.Query) == true)
                .Select(selector)
                .ToListAsync();

            return Ok(results);

TLDR. Full usage for my scenario.

Controller:

[HttpPost, Route("Search/Lessons")]
public async Task<IHttpActionResult> SearchLessons([FromBody] SearchQuery qry)
{
    var columnNames = GetColumNames<Lesson>(qry.TableList);

    if (columnNames.Count < 1) {
        return BadRequest();
    }

    var selector = "new(" + String.Join(", ", columnNames.ToArray()) + ")";

    var results = await db.Lessons
        .Where(x => x.LessonName.Contains(qry.Query) == true || x.HTML.Contains(qry.Query) == true)
        .Select(selector)
        .ToListAsync();

    return Ok(results);
}

And Helpers:..

private List<string> GetColumNames<T>(List<string> columnsToFetch = null)
{
    var properties = GetProperties<T>();
    var columnNames = new List<string>();

    if (columnsToFetch == null)
    {
        return properties;
    }

    foreach (var columnRequested in columnsToFetch)
    {
        var matchedColumn = properties
            .Where(x => x.ToLower() == columnRequested.ToLower())
            .FirstOrDefault();

        if (matchedColumn != default(string))
        {
            columnNames.Add(matchedColumn);
        }
    }

    return columnNames;
}

And..

private List<string> GetProperties<T>()
    {
        dynamic model = TypeDescriptor.GetProperties(Activator.CreateInstance<T>());

        List<string> result = new List<string>();
        foreach (var prop in model)
        {
            result.Add(prop.Name);
        }
        return result;
    }

Disclaimer : I'm the owner of the project Eval-Expression.NET

This library is not FREE but allows you to compile & execute C# code at runtime. By example, you can perform any LINQ dynamically

Tutorial: LINQ Dynamic

Example:

public searchEntities(string query, list<string> columnNames)
{
   await db.entities.SelectDynamic(x => "{" + string.Join(",", columnNames) + "}")
           .where(...)..ToListAsync();
}

I don't see how that would be possible.

First of all, to return the list from searchEntities, the select would need to create a named object.

What you could do is pass in a function-expression to searchEntities

public Task<List<T>>  searchEntities<T>(EXpression<Func<Entity, T> selector)
{
    return await db.entities.select(selector).where(...)..ToListAsync();
}


Expression<Func<Entity, AdHocClass1> selector1 = (Entity e)=> new AdHocClass1 {...};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM