EF 6 Core IEnumerable<T> Projection to Dynamic Object - linq

I have been struggling on this for a day now and I can't figure out how to do this:
I have an IEnumerable (Query generated in steps) that I'm taking in as a param (right after the where clause).
Base on column names List I'm trying create a dynamic select clause.
public static IQueryable<T> AddSelectClause<T>(SearchDto criteria, IQueryable<T> queryable, ControllerStateManager stateManager)
{
if (criteria.SelectColumns != null)
{
var formattedColumns = string.Join(", ", criteria.SelectColumns);
queryable.SelectMany(c => $"new({formattedColumns})");
}
return queryable;
}
Obviously this is not working, no exception just returns all columns.
I tried this:
queryable.Select(c => new { c.Id, c.Name });
Intellisense doesn't like this saying: T does not contain a definition for Id (same for name).
That is about my depth of knowledge on Linq, any suggestions would be greatly appreciated

Related

Creating a group by parameter for the get method in a generic repository for entity framework

I am using the repository pattern with Entity Framework as described in this article: repository pattern with Entity Framework
In the part where the GenericRepository is described (Generic Repository) there is a method which is used to get entities from the database set called Get. It has an orderBy but no groupBy. I am wondering how one might implement a groupBy in the same manner as the orderBy so that you can specify which field to group by dynamically on the entity.
What I have come up with is this:
Func<IQueryable<TEntity>, IGrouping<string, TEntity>> groupBy = null
and then in the method code it should be used something like this:
if(groupBy != null)
{
query = groupBy(query).ToList();
}
But this is not compiling since the IGrouping is not queryable. Does someone know how to point me in the right direction or has a solution to this?
Edit: The reason for doing this instead of using groupby on the returned list is for performance reasons. I want the groupby to be sent as an sql statement to the database and resolved there.
Grouping has no sense without projection. So you have to define new method which returns IEnumerable with new type.
I have added sample of such method. Also removed includeProperties because EF Core ignores Includes during grouping.
Usage sample:
_orderRepostory
.GetGrouped(e => e.UserId, g => new { UserId = g.Key, Count = g.Count()});
And implementation:
public class GenericRepository<TEntity> where TEntity : class
{
... // other code
public virtual IEnumerable<TResult> GetGrouped<TKey, TResult>(
Expression<Func<TEntity, TKey>> groupingKey,
Expression<Func<IGrouping<TKey, TEntity>, TResult>> resultSelector,
Expression<Func<TEntity, bool>>? filter = null)
{
var query = dbSet.AsQueryable();
if (filter != null)
{
query = query.Where(filter);
}
return query.GroupBy(groupingKey).Select(resultSelector);
}
}

Solve Linq expression using Distinct, GroupBy and Take

I am using Entity Framework, working with lambda expressions, I have to select number of selected records from the grid (_numRecords) which includes sorting, remove duplicates based on a column (Distinct by a column). Following is the code:
private IEnumerable<ReadViewModel> generateLocalData(IQueryable<ReadViewModel> query, [DataSourceRequest] DataSourceRequest dsRequest, int exportXml)
{
if (exportXml > 0)
query = query.GroupBy(x => x.Id).Select(x => x.FirstOrDefault());
//query = query.OrderByDescending(x => x.EventDate).GroupBy(x => x.Id).Select(x => x.FirstOrDefault());
//query = query.DistinctBy(x => x.Id).AsQueryable<ReadViewModel>();
query = query.OrderByDescending(x => x.EventDate);
query = query.ApplySorting(dsRequest.Groups, dsRequest.Sorts);
query = query.Take(this._numRecords);
List<ReadViewModel> data;
data = query.ToList();
return data;
}
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> knownKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (knownKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
There is data on the grid (which can have duplicate Id field) but when I export it to xml, it should be unique.
Problem is when Distinct is applied to lambda expression it destroys the sorting, and I am using Take() method so I need to use it before fetching data from DB.
I've used 2 approaches to acheive distinct: by GroupBy & Single/Select and DistinctBy from Extension methods. But I'm unable to make it work, using these methods, as sorting is destroyed.
private static IQueryable<T> DistinctBy<T, TKey>(this IQueryable<T> source, Expression<Func<T, TKey>> keySelector)
{
return source.GroupBy(keySelector).Select(grp => grp.First());
}
Group by the key you want to be distinct by, then take the first item from each group. You might need FirstOrDefault() with some providers that don't do well with First() within queries.
This is sub-optimal when used with linq-to-objects, but you have the optimal approach for that case in your question already.

Linq Order by when column name is dynamic and pass as a string to a function

I have a Linq (Entity Framework) Query as
function getData(string col_to_sort , bool IsAscending , int pageNo , int pageSize)
{
context.table_name.Skip(pageNo*pageSize).Take(pageSize).ToArray();
}
What i want is that if i pass the name of the column as a parameter to the function
and the order it will sort my query too.
Since my column name will be a string so we might need to convert it to ObjectQuery.
How can i achieve this?
Any help is appreciated
You can use Dynamic Linq:
string direction = IsAscending ? " ASC" : " DESC";
context.table_name.OrderBy(col_to_sort + direction).Skip(pageNo*pageSize).Take(pageSize).ToArray();
If you are using Dynamic Linq, then the accepted answer will work.
But If you don't want to add an extra library (Dynamic Linq), then you can pick my first approach. I will explain both the approaches where you have or don't have Dynamic Linq. You can select based on your preferences and choice.
First Approach: When you don't have Dynamic Linq:
If you are using using System.Linq; instead of using System.Linq.Dynamic.Core, then you can use this approach:
orderBy is the string and Student is the T (The Entity, in which we want to search).
Create a Utility class, something like this: (you can anytime covert to extension method If you wish.)
public static class LinqUtility
{
public static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
}
And you can use like this:
public async Task<IList<Student>> GetStudents(long groupId, string orderBy, Filter filter)
{
return await _context.Students.Where(x => x.StudentGroupId == groupId)
.OrderByDescending(LinqUtility.ToLambda<Student>(orderBy))
.Skip(filter.Skip)
.Take(filter.Take)
.ToListAsync();
}
Second Approach: When you have Dynamic Linq:
The Dynamic LINQ library exposes a set of extension methods on IQueryable corresponding to the standard LINQ methods at Queryable, and which accept strings in a special syntax instead of expression trees.
You need to include the Library separately. Include System.Linq.Dynamic.Core. The Author of this Library is not Microsoft.. As such no harm in using it.
So, this Library, provides you a method, that accepts the string.
You can pass comma separated values as well in string, this can be achieved by above code as well, but some changes would be required.
In the same example, just pass orderBy:
public async Task<IList<Student>> GetStudents(long groupId, string orderBy, Filter filter)
{
return await _context.Students.Where(x => x.StudentGroupId == groupId)
.OrderBy(orderBy)
.Skip(filter.Skip)
.Take(filter.Take)
.ToListAsync();
}

How to access data into IQueryable?

I have IQueryable object and I need to take the data inside the IQueryable to put it into Textboxs controls. Is this possible?
I try something like:
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.????
}
Update:
I'm doing this:
public IQueryable getData(String tableName, Hashtable myparams)
{
decimal id = 0;
if (myparams.ContainsKey("id") == true)
id = (decimal)myparams["id"];
Type myType= Type.GetType("ORM_Linq." + tableName + ", ORM_Linq");
return this.GetTable(tableName , "select * from Articu where id_tipo_p = '" + id + "'");
}
public IQueryable<T> GetTable<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T : class
{
return _datacontext.GetTable<T>().Where(predicate);
}
This returns a {System.Data.Linq.SqlClient.SqlProvider+OneTimeEnumerable1[ORM_Linq.Articu]}`
I don't see any method like you tell me. I see Cast<>, Expression, ToString...
EDIT: Updated based on additional info from your other posts...
Your getData method is returning IQueryable instead of a strongly typed result, which is why you end up casting it. Try changing it to:
public IQueryable<ORM_Linq.Articu> getData(...)
Are you trying to query for "Articu" from different tables?
With the above change in place, your code can be rewritten as follows:
ORM_Linq.Articu result = mydata.SingleOrDefault();
if (result != null)
{
TextBoxCode.Text = result.id.ToString();
TextBoxName.Text = result.descrip;
}
If you have a single result use SingleOrDefault which will return a default value if no results are returned:
var result = mydata.SingleOrDefault();
if (result != null)
{
textbox1.text = result.ProductName; // use the column name
}
else
{
// do something
}
If you have multiple results then loop over them:
foreach (var item in mydata)
{
string name = item.ProductName;
int id = item.ProductId;
// etc..
}
First, you should be using a strongly-typed version of IQueryable. Say that your objects are of type MyObject and that MyObject has a property called Name of type string. Then, first change the parameter mydata to be of type IQueryable<MyObject>:
public void setdata (IQueryable<MyObject> mydata)
Then we can write a body like so to actually get some data out of. Let's say that we just want the first result from the query:
public void setdata (IQueryable<MyObject> mydata) {
MyObject first = mydata.FirstOrDefault();
if(first != null) {
textbox1.Text = first.Name;
}
}
Or, if you want to concatenate all the names:
public void setdata(IQueryable<MyObject> mydata) {
string text = String.Join(", ", mydata.Select(x => x.Name).ToArray());
textbo1.Text = text;
}
Well, as the name suggests, an object implementing IQueryable is... Queryable! You'll need to write a linq query to get at the internal details of your IQueryable object. In your linq query you'll be able to pull out its data and assign bits of it where ever you'd like - like your text box.
Here's a great starting place for learning Linq.
I think you find the same mental struggle when coming from FoxPro and from DataSet. Really nice, powerful string-based capabilities(sql for query, access to tables and columns name) in these worlds are not available, but replaced with a compiled, strongly-typed set of capabilities.
This is very nice if you are statically defining the UI for search and results display against a data source known at compile time. Not so nice if you are trying to build a system which attaches to existing data sources known only at runtime and defined by configuration data.
If you expect only one value just call FirstOrDefault() method.
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.FirstOrDefault().PropertyName;
}

Trouble with a LINQ 'filter' code throwing an error

I've got the following code in my Services project, which is trying to grab a list of posts based on the tag ... just like what we have here at SO (without making this a meta.stackoverflow.com question, with all due respect....)
This service code creates a linq query, passes it to the repository and then returns the result. Nothing too complicated. My LINQ filter method is failing with the following error :-
Method 'Boolean
Contains(System.String)' has no
supported translation to SQL.
I'm not sure how i should be changing my linq filter method :( Here's the code...
public IPagedList<Post> GetPosts(string tag, int index, int pageSize)
{
var query = _postRepository.GetPosts()
.WithMostRecent();
if (!string.IsNullOrEmpty(tag))
{
query = from q in query
.WithTag(tag) // <--- HERE'S THE FILTER
select q;
}
return query.ToPagedListOrNull(index, pageSize);
}
and the Filter method...
public static IQueryable<Post> WithTag(this IQueryable<Post> query,
string tag)
{
// 'TagList' (property) is an IList<string>
return from p in query
where p.TagList.Contains(tag)
select p;
}
Any ideas? I'm at a loss :(
Try with Any:
public static IQueryable<Post> WithTag(this IQueryable<Post> query,
string tag)
{
// 'TagList' (property) is an IList<string>
return from p in query
where p.TagList.Any(t => t == tag)
select p;
}
.
UPDATE (by PureKrome)
Another suggestion by Ahmad (in a comment below). This uses the Contains method so it will return all posts that contain the tag 'Test', eg. Post with Tag 'Testicle' :-
public static IQueryable<Post> WithTag(this IQueryable<Post> query,
string tag)
{
// 'TagList' (property) is an IList<string>
return from p in query
where p.TagList.Any(t => t.Contains(tag))
select p;
}
In WithTag try changing the query to use a List rather than an IList:
return from p in query
let taglist = p.TagList as List<string>
where taglist.Contains(tag)
select p;
Also check out this answer, which is similar to my suggestion: Stack overflow in LINQ to SQL and the Contains keyword

Resources