10

I have an existing function like this

public int sFunc(string sCol , int iId)
{
    string sSqlQuery = "  select  " + sCol + " from TableName where ID = " +  iId ;
    // Executes query and returns value in column sCol
}

The table has four columns to store integer values and I am reading them separately using above function.

Now I am converting it to Entity Framework .

public int sFunc(string sCol , int iId)
{
     return Convert.ToInt32(TableRepository.Entities.Where(x => x.ID == iId).Select(x => sCol ).FirstOrDefault());
}

but the above function returns an error

input string not in correct format

because it returns the column name itself.

I don't know how to solve this as I am very new to EF.

Any help would be appreciated

Thank you

2
  • 1
    So, you want at runtime to determine which columns to select? Do I have that correctly? Commented Jan 13, 2014 at 6:12
  • @TiesonT. yes exactly Commented Jan 13, 2014 at 6:18

6 Answers 6

6

Not going to be useful for the OP 8 years later, but this question has plenty of views, so I thought it could be helpful for others to have a proper answer.

If you use Entity Framework, you should do Linq projection (Select()), because that leads to the correct, efficient query on the db side, instead of pulling in the entire entity.

With Linq Select() you normally have to provide a lambda, though, so having your your column/property name in a string poses the main difficulty here.

The easiest solution is to use Dynamic LINQ (EntityFramework.DynamicLinq Nuget package). This package provides alternatives to the original Linq methods, which take strings as parameters, and it translates those strings into the appropriate expressions.

Example:

async Task<int> GetIntColumn(int entityId, string intColumnName)
{
    return await TableRepository.Entities
        .Where(x => x.Id == entityId)
        .Select(intColumnName) // Dynamic Linq projection
        .Cast<int>()
        .SingleAsync();
}

I also made this into an async call, because these days all database calls should be executed asynchronously. When you call this method, you have to await it to get the result (i.e.: var res = await GetIntColumn(...);).

Generic variation

Probably it's more useful to change it into an extension method on IQueryable, and make the column/property type into a generic type parameter, so you could use it with any column/property:

(Provided you have a common interface for all your entities that specifies an Id property.)

public static async Task<TColumn> GetColumn<TEntity, TColumn>(this IQueryable<TEntity> queryable, int entityId, string columnName)
    where TEntity : IEntity
{
    return await queryable
        .Where(x => x.Id == entityId)
        .Select(columnName) // Dynamic Linq projection
        .Cast<TColumn>()
        .SingleAsync();
}

This is called like this: var result = await TableRepository.Entities.GetColumn<Entity, int>(id, columnName);

Generic variation that accepts a list of columns

You can extend it further to support selecting multiple columns dynamically:

public static async Task<dynamic> GetColumns<TEntity>(this IQueryable<TEntity> queryable, int entityId, params string[] columnNames)
    where TEntity : IEntity
{
    return await queryable
        .Where(x => x.Id == entityId)
        .Select($"new({string.Join(", ", columnNames)})")
        .Cast<dynamic>()
        .SingleAsync();
}

This is called like this: var result = await TableRepository.Entities.GetColumns(id, columnName1, columnName2, ...);.

Since the return type and its members are not known compile-time, we have to return dynamic here. Which makes it difficult to work with the result, but if all you want is to serialize it and send it back to the client, it's fine for that purpose.

Sign up to request clarification or add additional context in comments.

1 Comment

The type argument for method Enumerable.Select... cannot be inferred from the usage is what I get when I try .Select(columnName). Using EF Core though
2

We had the same struggle for years because of Entity Framework 6. In our case, we have datagrid having 300 columns and EF 6 was generating SQL query with 2000 lines of code. We tried the same optimization as describing below there as well, but we ended up in worse performance.

A huge boost comes with EF Core (we tested it on EF Core 7).

our queries are usually built like:

db.MyDbSet
   .GetOverviewQuery()
   .SelectColumns(selectedColumnNames)
   .ToArrayAsync(...);

where method GetOverviewQuery() contains huge LINQ select returning IQueryable. TResult is just a DTO class with no logic.

We introduced a method SelectColumns() which receives a list of selected property names from the DTO class.

private static IQueryable<TResult> SelectColumns<TResult>(this IQueryable<TResult> source, HashSet<string> selectedColumnNames)
        {
            var props = GetProperties<TResult>(selectedColumnNames);
            var sourceType = source.ElementType;
            var resultType = typeof(TResult);
            var parameter = Expression.Parameter(sourceType, "e");
            var bindings = props
                .Select(prop => Expression.Bind(prop, Expression.PropertyOrField(parameter, prop.Name)));
            var body = Expression.MemberInit(Expression.New(resultType), bindings);
            var selector = Expression.Lambda(body, parameter);
            return source.Provider.CreateQuery<TResult>(
                Expression.Call(typeof(Queryable), "Select", new Type[] { sourceType, resultType },
                    source.Expression, Expression.Quote(selector)));
        }
        
        private static IEnumerable<PropertyInfo> GetProperties<TItem>(HashSet<string> selectedColumnNames)
        {
            return typeof(TItem)
                // Make sure you can use only valid columns with setter
                .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty)
                .Where(p => p.CanWrite)
                .Where(p => !selectedColumnNames.Contains(p.Name));
        }

In the end, it reduces the final SQL size because EF Core removes all the columns that are not in our column selection. This optimize also nested selects and joins. If you then select for example only 10 columns, the original query for all columns is reduced from 2000 lines of SQL to 30 lines where only the necessary data will be considered. That boosts Expression tree building as well as the SQL server query.

I wish we could have used EF Core earlier on our project :D Hope it will help :)

Comments

1

This might help to solve your problem:

public int sFunc(string sCol, int iId)
{
    var _tableRepository = TableRepository.Entities.Where(x => x.ID == iId).Select(e => e).FirstOrDefault();
    if (_tableRepository == null) return 0;

    var _value = _tableRepository.GetType().GetProperties().Where(a => a.Name == sCol).Select(p => p.GetValue(_tableRepository, null)).FirstOrDefault();

    return _value != null ? Convert.ToInt32(_value.ToString()) : 0;
}

This method now work for dynamically input method parameter sCol.

Update: This is not in context of current question but in general how we can select dynamic column using expression:

var parameter = Expression.Parameter(typeof(EntityTable));
var property = Expression.Property(parameter, "ColumnName");
//Replace string with type of ColumnName and entity table name.
var selector = Expression.Lambda<Func<EntityTable, string>>(property, parameter);

//Before using queryable you can include where clause with it. ToList can be avoided if need to build further query.
var result = queryable.Select(selector).ToList();

5 Comments

Thank you for your help , but this gives me an error (TableRepository.Entities.Entity does not contain a definition for 'sCol' )
That perfectly did the trick and got me exactly what I wanted.Thank you
@ArekBal What are you trying to say? Yes it is related to Entity Framework since I am providing Linq on collection TableRepository.Entities which is collection of EntityObject, Please explain your problem or provide me link for to your question so that I can find something for you. And yes it is not dynamic linq plz ref Naveen answer for same.
This one still hits the database asking all columns.
Note that this is a really terrible answer if you're dealing with Entity Framework. This is still going to retrieve all data from the database and also requires that you have a fixed entity type.
0

You have to try with dynamic LINQ. Details are HERE

2 Comments

This answer could be greatly improved if you provided a summary of the linked webpage (in case the link breaks for some reason) or if you added a code sample using the tool detailed on the webpage.
There is no explanation about subject on the link you provided.
-1

Instead of passing the string column name as a parameter, try passing in a lambda expression, like:

sFunc(x => x.FirstColumnName, rowId);
sFunc(x => x.SecondColumnName, rowId);
...

This will in the end give you intellisense for column names, so you avoid possible errors when column name is mistyped.

More about this here: C# Pass Lambda Expression as Method Parameter

However, if you must keep the same method signature, i.e. to support other/legacy code, then you can try this:

public string sFunc(string sCol , int iId)
{
    return TableRepository.Entities.Where(x => x.ID == iId).Select(x => (string) x.GetType().GetProperty(sCol).GetValue(x)});
}

You might need to adjust this a bit, I didn't have a quick way of testing this.

2 Comments

Thank you for your help ,right now I cannot change my function parameters. So your second option is suitable for me. I have tried that and got an error (LINQ to Entities does not recognize the method 'System.Object GetValue) .
I removed the second parameter from GetValue. Try updated code. BTW, you might want to add some error handling, i.e. what happens is the property by the given name is not found - a misspelled column name, perhaps.
-1

You can do this:

        var entity = _dbContext.Find(YourEntity, entityKey);
        // Finds column to select or update
        PropertyInfo propertyInfo = entity.GetType().GetProperty("TheColumnVariable");

1 Comment

Welcome to Stack Overflow! Please note you are answering a very old and already answered question. Here is a guide on How to Answer.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.