I am having a class Item.
class Item{
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public string Name { get; set; }
public string Description { get; set;}
}
I want to filter list of items based on dynamic column name.
Suppose I want list of Names then Column Name is "Name" and result will be list of names
If column name is Description, I need list of descriptions.
How to do this with LinQ?
Easy, just select the property you need from the list:
var items = new List<Item>();
//get names
var names = items.Select(x => x.Name);
//get descriptions
var descriptions = items.Select(x => x.Description);
Update:
You'll need a bit of reflection to do this:
var names = items.Select(x => x.GetType().GetProperty("Name").GetValue(x));
Throw this in a method for re-usability:
public IEnumerable<object> GetColumn(List<Item> items, string columnName)
{
var values = items.Select(x => x.GetType().GetProperty(columnName).GetValue(x));
return values;
}
Of course this doesn't validate wether the column exists in the object. So it will throw a NullReferenceException when it doesn't. It returns an IEnumerable<object>, so you'll have to call ToString() on each object afterwards to get the value or call the ToString() in the query right after GetValue(x):
public IEnumerable<string> GetColumn(List<Item> items, string columnName)
{
var values = items.Select(x => x.GetType().GetProperty(columnName).GetValue(x).ToString());
return values;
}
Usage:
var items = new List<Item>(); //fill it up
var result = GetColumn(items, "Name");
Related
I have an one-to-many relation in my entities:
public class Header
{
public Header()
{
Items = new List<Item>
}
public int Id {get; set;}
public virtual ICollection<Item> Items {get; set;}
// other properties
}
public class Item
{
public int Id {get; set;}
public virtual Header Header { get; set; }
public string Title { get; set; }
public DateTime CreationDate { get; set; }
public int Weight { get; set; }
}
I want to load Header and some of its Items, so I wrote this linq to entity query(EF6):
using(var ctx = new MyContext())
{
var result = ctx.Headers.Where(someConditions)
.AsNoTracking()
.Select(header => new {
HeaderId = header.Id,
//fetch other header properties here
LastItemCreationDate = header.Items.OrderByDescending(item => item.CreationDate)
.Select(item => item.Title)
.FirstOrDefault(),
LastItemTitle = header.Items.OrderByDescending(item => item.CreationDate)
.Select(item => item.CreationDate)
.FirstOrDefault(),
LastItemWeight = header.Items.OrderByDescending(item => item.CreationDate)
.Select(item => item.Weight)
.FirstOrDefault()
}).ToList();
}
This query generate a sql script with 3 times join Header and Item tables, is there any more efficent way to write this query to join Header and Item tables one time?
Since you are using Select, you don't need AsNoTracking since the resulting query will not load any entities. The key performance impacts in your case would be indexes in the Header table suitability for your Where clause, then also whether there is a Descending Index available on the CreationDate in the Items table.
Another improvement would be to alter the projection slightly:
var result = ctx.Headers.Where(someConditions)
.Select(header => new {
HeaderId = header.Id,
LatestItem = header.Items
.OrderByDescending(item => item.CreatedDate)
.Select(item => new
{
Title = item.Title,
CreationDate = item.CreationDate,
Weight = item.Weight
}).FirstOrDefault()
}).ToList();
This will change the resulting anonymous type structure a bit, but should result in a nicer, single join.
You will get a result.HeaderId, then a null-able result.LastestItem containing the latest item's properties. So result.LatestItem?.Title instead of result.Title.
I have a Linq query which is selecting 2 columns(that can be any 2 from all columns) dynamically based on some condition.I need to map the query result in to below model irrespective of selected column names
public class FinalModel
{
public string Text { get; set; }
public string Id { get; set; }
}
Currently I am using reflection to map the result in to that model because i am getting some anonymous list of objects and it is working fine, But I want to remove that reflection and need to add the mapping in the select itself, my current implementation is like below
string column1 = "Name" //can be other columns also
string column2 = "Age"
var result = _context.table1
.Select("new ("+ column1 +","+ column2 +")")
.Distinct()
.Take(10) // having more records in table
.ToDynamicList()
.Select(x => new FinalModel()
{
Id = x.GetType().GetProperty(column1).GetValue(x).ToString(),
Text = x.GetType().GetProperty(column2).GetValue(x).ToString(),
});
The above code is working fine but I need to remove the below section
.Select(x => new FinalModel()
{
Id = x.GetType().GetProperty(column1).GetValue(x).ToString(),
Text = x.GetType().GetProperty(column2).GetValue(x).ToString(),
});
Is there any way to remove the refletion and add that model mapping directly inside Select("new (column1,column2)")
Is there any way to add orderBy with Column2 variable?
You can use generic versions of Select and ToDynamicList and OrderBy($"{column2}") for sorting:
var result = _context.table1
.Select<FinalModel>($"new ({column1} as Id, {column2} as Text)")
.Distinct()
.OrderBy("Text")
.Take(10)
.ToDynamicList<FinalModel>();
Or if you want to stick with dynamic:
var result = _context.table1
.Select($"new ({column1}, {column2})")
.Distinct()
.OrderBy($"{column2}")
.Take(10)
.ToDynamicList()
.Select(d => new FinalModel()
{
Id = d[column1].ToString(),
Text = d[column2].ToString(),
})
.ToList();
You need to use .Select<T> instead of just .Select() to make sure that the selected entity the correct type. So in your case you need .Select<FinalModel>.
Use the as cast operator to "rename" the properties from the source-entity to the destination entity (FinalModel)
If you want the result to be typed, also use .ToDynamicList<FinalModel>().
Full example code below:
using System.Linq.Dynamic.Core;
class Program
{
static void Main(string[] args)
{
var myQuery = new[] { new XModel { Age = "1", Name = "n" } }.AsQueryable();
string column1 = "Name";
string column2 = "Age";
var result = myQuery
.Select<FinalModel>("new (" + column1 + " as Id, " + column2 + " as Text)")
.Distinct()
.Take(10)
.ToDynamicList<FinalModel>();
Console.WriteLine(result[0].Id + " " + result[0].Text);
}
public class XModel
{
public string Name { get; set; }
public string Age { get; set; }
}
public class FinalModel
{
public string Text { get; set; }
public string Id { get; set; }
}
}
I have been struggling with this for several days. I need to populate a dropdownlistfor with genres.
My MovieRepository to grab the genres:
public IQueryable<Movies> MoviesAndGenres
{
get { return db.Movies.Include(m => m.parentGenre); }
}
My movie model
public virtual Genres parentGenre { get; set; }
Genre Model:
public class Genres
{
public Genres()
{
this.movies = new HashSet<Movies>();
}
[Key]
public int genreId { get; set; }
[Required(ErrorMessage = "A genre name is required")]
[StringLength(25)]
public String genreName { get; set; }
public ICollection<Movies> movies { get; set; }
}
I am trying to pass in the genres with a select list, but I am getting a LINQ to Entities does not recognize the System.String To String() Method, and this method cannot be translated to a stored expression.
Movies Controller, addMovie action:
ViewBag.Genres = movieRepository.MoviesAndGenres.Select(m => new SelectListItem
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId.ToString()
}).ToList();
return View();
View:
#Html.DropDownListFor(m => m.parentGenre, (SelectList)ViewBag.Genres)
Any help would be greatly appreciated!
Update:
Repository:
public IQueryable<Genres> MoviesAndGenres
{
get { return db.Genres; }
}
Controller:
var x = movieRepository.MoviesAndGenres.Select(m => new
{
Text = m.genreName,
Value = m.genreId
});
ViewBag.Genres = new SelectList(x);
return View();
View:
#Html.DropDownListFor(m => m.parentGenre, (SelectList)ViewBag.Genres)
Since you're retrieving all of the records anyways, you can just do this.
ViewBag.Genres = movieRepository.MoviesAndGenres.AsEnumerable()
.Select(m => new SelectListItem
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId.ToString()
});
You would also need to change your view to:
#Html.DropDownListFor(m => m.parentGenre, new SelectList(ViewBag.Genres))
Actually, a better approach would probably be this, since then it only retrieves the specific columns you need:
var x = movieRepository.MoviesAndGenres.Select(m => new
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId
});
ViewBag.Genres = new SelectList(x)
Also, the ToList() is no longer required because it's already in a an immediate state.
I've written a LINQ query shown below :
List<Actions> actions = resourceActions.Actions.Select(s => s.ActionName).ToList();
How do I give for selecting multiple columns here ? ie I want to add columns s.ActionId and s.IsActive. I'm unable to apply it.
Make a class to represent the data you want:
public class ResourceAction
{
public int Id {get;set;}
public string Name {get; set; }
}
Select a list of those instead:
List<ResourceAction> actions = resourceActions.Actions
.Select(s => new ResourceAction() { Id = s.Id, Name = s.ActionName}).ToList();
I believe this is what your looking for. However you need to change the output to an anonymous type.
var actions = resourceActions.Actions.Select(s => new { s.ActionName, s.ActionId, s.IsActive } ).ToList();
You can use a anonymous type for this, for example
var actions = resourceActions.Actions.Select(s =>
new { Id = s.Id, Name = s.ActionName, Active = s.IsActive).ToList();
but a better way would be to create a class like
public class ActionWithId
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
}
List<ActionWithId> actions = resourceActions.Actions.Select(s =>
new ActionWithId() { Id = s.Id, Name = s.ActionName, Active = s.IsActive }).ToList();
I have a method that selects two fields from database where text in first field match some value
public static List<List<string>> SelectForSearch(string letter)
{
var data = (from p in model.City
where p.Name.StartsWith(letter)
select new List<string> { p.Name, p.CountryName }).ToList();
return data;
}
But it returns me a list like this:
[0][0]Australia
[0][1]Ballina
[1][0]Berry
[1][1]Australia
[2][0]Australia
[2][1]Bendigo
...
Country and City possition don't have a static index like this:
[0][0]Ballina
[0][1]Australia
[1][0]Berry
[1][1]Australia
[2][0]Bendigo
[2][1]Australia
...
Your issue is that in your select statement, instead of creating a type with Name and CountryName you are creating a List of strings. The List initialiser allows you to pass in the values when the list is constructed by placing them in { } and you are using this ability by accident, as you saw it creates a list of strings where the name is the first element and the country name is the second element. What you want to be doing is more like:
var data = (from p in model.City
where p.Name.StartsWith(letter)
select new { City = p.Name, CountryName = p.CountryName }).ToList();
return data;
This is using anonymous types which is not good as you want to declare a type for the return value. So you should really create a class for storage, for example:
public class CityCountryPair
{
public String City { get; set; }
public String CountryName { get; set; }
}
then your method becomes
public static List<CityCountryPair> SelectForSearch(string letter)
{
var data = (from p in model.City
where p.Name.StartsWith(letter)
select new CityCountryPair() { City = p.Name,
CountryName = p.CountryName
}).ToList();
return data;
}