I have a linq query where I’d like to dynamically select only the fields requested by my user.
Currently I’m mapping my Jobs to a data transformation object like this:
var jobs = (from p in jobsDB
select new JobReportDTO()
{
JobID = p.JobID,
EventType = p.EventType,
DateApproved = p.ApprovedDate,
DateEntered = p.EnteredDate,
DateClosed = p.ClosedDate,
StartDate = p.StartDate,
FinishDate = p.FinishDate,
InsuredName = p.InsuredName,
StreetAddress = p.StreetAddress,
Suburb = p.Suburb,
State = p.State,
Postcode = p.Postcode,
.... etc
Within this function I have a number of boolean variables that identify whether that field should be sent to the view, i.e.:
public bool ShowInsuredName { get; set; }
public bool ShowSuburb { get; set; }
public bool ShowICLA { get; set; }
public bool ShowClaimNumber { get; set; }
public bool ShowFileMananger { get; set; }
public bool ShowSupervisor { get; set; }
public bool ShowStatus { get; set; }
... etc
How can I modify my linq query to show selected fields only?
I’ve tried
var jobs = (from p in jobsDB
select new JobReportDTO()
{
JobID = p.JobID,
jobReport.ShowEventType == true ? EventType = p.EventType : "",
... etc
But am getting “invalid initialiser member declarator”
If you can afford LINQ method syntax and use strong naming convention for options like public bool Show{DTOPropertyName} { get; set; }, then you can make your life much easier with the help of the System.Linq.Expressions and the following little helper method
public static class MyExtensions
{
public static IQueryable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, object options)
{
var memberInit = (MemberInitExpression)selector.Body;
var bindings = new List<MemberBinding>();
foreach (var binding in memberInit.Bindings)
{
var option = options.GetType().GetProperty("Show" + binding.Member.Name);
if (option == null || (bool)option.GetValue(options)) bindings.Add(binding);
}
var newSelector = Expression.Lambda<Func<TSource, TResult>>(
Expression.MemberInit(memberInit.NewExpression, bindings), selector.Parameters);
return source.Select(newSelector);
}
}
What it does is to remove the assignments which has associated ShowProperty with value set to false.
The usage is simple
var jobs = jobsDB.Select(p => new JobReportDTO
{
JobID = p.JobID,
EventType = p.EventType,
DateApproved = p.ApprovedDate,
DateEntered = p.EnteredDate,
DateClosed = p.ClosedDate,
StartDate = p.StartDate,
FinishDate = p.FinishDate,
InsuredName = p.InsuredName,
StreetAddress = p.StreetAddress,
Suburb = p.Suburb,
State = p.State,
Postcode = p.Postcode,
.... etc
}, jobReport);
If you set a breakpoint in the debugger and examine the newSelector variable, you'll see that only properties that do not have ShowProperty (like JobID) or have ShowProperty = true are included.
Try this way:
EventType = jobReport.ShowEventType == true ? p.EventType : string.Empty,
Related
I've got this class with a collection of revisions:
public class Client : BaseEntity
{
public virtual ICollection<Draw> Draws { get; set; }
public virtual ICollection<ClientRevision> ClientRevisions { get; set; }
}
public class ClientRevision : BaseEntity
{
public Guid ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double? Increase { get; set; }
public string RevisionCode { get; set; }
public string RevisionNote { get; set; }
}
On controller, I'd like to retrieve the list of clients with the last revision available. I've got a base repository with the following get method:
public async Task<IEnumerable<T>> GetAsync(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
IEnumerable<Expression<Func<T, object>>> includeProperties = null,
int? pageIndex = null,
int? itemsPerPage = null,
bool ignoreQueryFilter = true,
bool ignoreTracking = true)
{
var query = DataContext.GetData<T>(ignoreTracking);
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
if (ignoreQueryFilter)
{
query = query.IgnoreQueryFilters();
}
query = ApplyIncludePropertiesIfNeeded(includeProperties, query);
if (pageIndex.HasValue && itemsPerPage.HasValue)
{
query = query
Skip(pageIndex.Value * itemsPerPage.Value)
Take(itemsPerPage.Value + 1);
}
return await query.ToListAsync();
}
I'd like to order by the list by the Name of the client; the name is in the Client Revision. I try to implement the following filter and orderby, but I cannot create a correct linq expression for retrive the Name of the Client.
public async Task<ActionResult<ClientResponse>> GetClientListAsync()
{
var result = new List<ClientResponse>();
List<Expression<Func<Client, object>>> includes = new() { i => i.Draws };
var sortOn = "ClientRevisions.Name";
var param = Expression.Parameter(typeof(Client), "client");
var parts = sortOn.Split('.');
Expression parent = param;
foreach (var part in parts)
{
parent = Expression.Property(parent, part);
}
var sortExpression = Expression.Lambda<Func<Client, object>>(parent, param);
Func<IQueryable<Client>, IOrderedQueryable<Client>> order = o => o.OrderBy(sortExpression);
}
The error is the following:
Instance property 'Name' is not defined for type 'System.Collections.Generic.ICollection`1[ClientRevision]' (Parameter 'propertyName')
Because is a list. How to retrieve this data from a list? Thanks
Following situation:
Table of users
Table of addresses
The user has a single optional reference to the address table (=left join)
The query to fetch the data is:
IQueryable<User> query =
from u in _dbContext.Users
join a in _dbContext.Address on u.AddressId equals a.Id into address
from addresses in address.DefaultIfEmpty()
select new User(u, a);
Now I want to do a sorting on the query based on the municipality of the address
query = query.OrderBy(u => u.Address.Municipality);
The problem is that the Address can be a null value (as the address is optional) and for that reason it is throwing following exception.
NullReferenceException: Object reference not set to an instance of an object.
Is there a way to order on the municipality knowing that it can be null?
Already tried following things with same outcome:
query = query.OrderBy(u => u.Address.Municipality ?? "");
query = query.OrderBy(u => u.Address == null).ThenBy(u => u.Address.Municipality);
You can use
query = query.OrderBy(u => u.Address.Municipality.HasValue);
You can write your comparer like this:
public class One
{
public int A { get; set; }
}
public class Two
{
public string S { get; set; }
}
public class T
{
public One One { get; set; }
public Two Two { get; set; }
}
public class OrderComparer : IComparer<Two>
{
public int Compare(Two x, Two y)
{
if (((x == null) || (x.S == null)) && ((y == null) || (y.S == null)))
return 0;
if ((x == null) || (x.S == null))
return -1;
if ((y == null) || (y.S == null))
return 1;
return x.S.CompareTo(y.S);
}
}
static void Main(string[] args)
{
var collection = new List<T> {
new T{ One = new One{A=1}, Two = new Two{ S="dd" } },
new T{ One = new One{A=5}, Two = null },
new T{ One = new One{A=0}, Two = new Two{ S=null } },
new T{ One = new One{A=6}, Two = new Two{ S="aa" } },
};
var comparer = new OrderComparer();
collection = collection.OrderBy(e => e.Two, comparer).ToList();
}
But in your case you have to write var collection = query.AsEnumerable().OrderBy(x=>x.Address).
Also there is other method:
var result = query.Where(x=>x.Address==null || x.Address.Municipality==null)
.Union(query.Where(x.Address!=null && x.Address.Municipality!=null).OrderBy(x=>x.Address.Municipality));
I create a simple demo and it works well when I add nullable foreign key to the two tables.
Besides, I do not understabd what is select new User(u, a); in your code.
Below is my sample code:
Models:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Address")]
public int? AddressId { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string AddressName { get; set; }
public string Municipality { get; set; }
}
Action:
IQueryable<User> query =
from u in _context.Users
join a in _context.Address on u.AddressId equals a.Id into address
from addresses in address.DefaultIfEmpty()
select new User
{
Id = u.Id,
Name = u.Name,
AddressId = u.AddressId,
Address = addresses
};
query = query.OrderBy(u => u.Address.Municipality);
I am trying to get multiple results into a list to send back to JS to populate a grid. The first query (_mappedQuery) is getting data. I then want to end up putting the values into the _udfList object. I keep getting variances on the error 'cannot convert queryable to class'
I have tried setting as lists, creating query objects, single class objects. All no luck so far
MemberMNCFormsList _udfList = new MemberMNCFormsList();
foreach (var _row in _udfTables) {
System.Diagnostics.Debugger.Break();
System.Diagnostics.Debugger.Launch();
var _mappedQuery = (from res in Query<UdfColumnMapping>().AsNoTracking()
join udf in Query<UserDefinedForms>().AsNoTracking() on res.Func_Area equals udf.Func_Area
join ds in Query<Data_Set>().AsNoTracking() on res.Data_ID equals ds.DATA_ID
join df in Query<DEFITEM>().AsNoTracking() on ds.DEF_ID equals df.DEF_ID
where udf.UserDefinedForms_ID == _row.UserDefinedForms_ID &&
(res.FieldName.ToLower().StartsWith("reviewname") ||
res.FieldName.ToLower().StartsWith("disposition") ||
res.FieldName.ToLower().StartsWith("reviewdate"))
select (new MemberMNCForms {
UserDefinedFormData_ID = _row.UserDefinedFormData_ID,
FormId = udf.UserDefinedForms_ID,
MappedColumnName = res.MappedColumnName,
FieldName = res.FieldName,
MappedTableName = res.MappedTableName,
Reviewed_Name = _row.LAST_NAME.Trim() + ", " + _row.FIRST_NAME.Trim(),
Reviewed_Date = _row.CreateDate.GetShortDateorEmpty().ToString()
}));
var _formRow = _mappedQuery.Select(t => new MemberMNCForms {
UserDefinedFormData_ID = t.UserDefinedFormData_ID,
FormId = t.FormId,
MappedColumnName = t.MappedColumnName,
FieldName = t.FieldName,
MappedTableName = t.MappedTableName,
Reviewed_Name = t.Reviewed_Name,
Reviewed_Date = t.Reviewed_Date
})));
_udfList.list.Add(_formRow);
public sealed class MemberMNCForms {
public Guid? UserDefinedFormData_ID { get; set; }
public int FormId { get; set; }
public string Reviewed_Name { get; set; }
public string MappedColumnName { get; set; }
public string FieldName { get; set; }
public string MappedTableName { get; set; }
public int? MNCDetermination_ID { get; set; }
public string Reviewed_By { get; set; }
public string Reviewed_Date { get; set; }
}
public sealed class MemberMNCFormsList : ErrorInfo
{
public List<MemberMNCForms> list = new List<MemberMNCForms>();
public int Count { get; set; }
}
I am trying to get the _udfList object populated with the values coming from _mappedQuery. The only thing I thought would work was to create a MemberMNCForms object for each record in _mappedQuery to then add to _udfList.list
_formRow is an IEnumerable<MemberMNCForms>
var _formRow = _mappedQuery.Select(t => new MemberMNCForms {
UserDefinedFormData_ID = t.UserDefinedFormData_ID,
FormId = t.FormId,
MappedColumnName = t.MappedColumnName,
FieldName = t.FieldName,
MappedTableName = t.MappedTableName,
Reviewed_Name = t.Reviewed_Name,
Reviewed_Date = t.Reviewed_Date
})));
Here you are trying to add an IEnumerable<MemberMNCForms> to a List<MemberMNCForms>
_udfList.list.Add(_formRow);
You can't do this with .Add. You have to use .AddRange
Try this:
_udfList.list.AddRange(_formRow);
When you use linq like that, even if there is a single item in the list that you are Selecting on, it is just an expression tree until it is iterated on.
I assume that you are expecting a collection of MemberMNCForms back so you would need use AddRange instead of Add
_udfList.list.AddRange(_formRow);
To make sure that it has been executed, you can use ToList
_udfList.list.AddRange(_formRow.ToList());
If you are just expecting a single result, you can use SingleOrDefault.
var result = _formRow.SingleOrDefault();
if (result != null) {
_udfList.list.Add(result);
}
I have this :
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
public int ZipCode { get; set; }
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? Age { get; set; }
public City City { get; set; }
public Company Company { get; set; }
}
I'd like a some case generate the predicate like this :
var result = listPerson.Where(x => x.Age == 10).ToList<>();
Or this :
var result = listPerson.Where( x => x.Company.Name == 1234).ToList();
Or this :
var result = listPerson.Where( x => x.City.ZipCode == "MyZipCode").ToList();
Or this :
var result = listPerson.Where( x => x.Company.Name == "MyCompanyName").ToList();
Then I created a "PredicateBuilder", that's work (I get the type, if nullable or not and I build the predicate) when I do this :
BuildPredicate<Person>("Age", 10); I get this : x => x.Age == 10
But I don't how manage when there is an nested property like this :
BuildPredicate<Person>("City.ZipCode", "MyZipCode");
I'd like get this : x => x.City.ZipCode == "MyZipCode"
Or this :
BuildPredicate<Person>("City.Name", "MyName");
I'd like get this : x => x.City.Name == "MyName"
Or this :
BuildPredicate<Person>("Company.Name", "MyCompanyName");
I'd like get this : x => x.Company.Name == "MyCompanyName"
(not intending to duplicate Jon - OP contacted me to provide an answer)
The following seems to work fine:
static Expression<Func<T,bool>> BuildPredicate<T>(string member, object value) {
var p = Expression.Parameter(typeof(T));
Expression body = p;
foreach (var subMember in member.Split('.')) {
body = Expression.PropertyOrField(body, subMember);
}
return Expression.Lambda<Func<T, bool>>(Expression.Equal(
body, Expression.Constant(value, body.Type)), p);
}
The only functional difference between that and Jon's answer is that it handles null slightly better, by telling Expression.Constant what the expected type is. As a demonstration of usage:
static void Main() {
var pred = BuildPredicate<Person>("City.Name", "MyCity");
var people = new[] {
new Person { City = new City { Name = "Somewhere Else"} },
new Person { City = new City { Name = "MyCity"} },
};
var person = people.AsQueryable().Single(pred);
}
You just need to split your expression by dots, and then iterate over it, using Expression.Property multiple times. Something like this:
string[] properties = path.Split('.');
var parameter = Expression.Parameter(typeof(T), "x");
var lhs = parameter;
foreach (var property in properties)
{
lhs = Expression.Property(lhs, property);
}
// I've assumed that the target is a string, given the question. If that's
// not the case, look at Marc's answer.
var rhs = Expression.Constant(targetValue, typeof(string));
var predicate = Expression.Equals(lhs, rhs);
var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameter);
I'm driving myself crazy trying to understand Expressions in LINQ. Any help is much appreciated (even telling me that I'm totally off base here).
Let's say I have three classes
public class Person
{
public string Name { get; set;}
public IEnumerable<PersonLocation> Locations { get; set;}
public IEnumerable<PersonEducation> Educations { get; set:}
}
public class PersonLocation
{
public string Name { get; set;}
public string Floor { get; set;}
public string Extension { get; set;}
}
public class PersonEducation
{
public string SchoolName { get; set;}
public string GraduationYear { get; set;}
}
I'm trying to create a method that takes in a string, such as Locations.Name or Locations.Floor, or Educations.SchoolName which will then create a dynamic linq query
IEnumerable<Person> people = GetAllPeople();
GetFilteredResults(people, "Location.Name", "San Francisco");
GetFilteredResults(people, "Location.Floor", "17");
GetFilteredResults(people, "Educations.SchoolName", "Northwestern");
This GetFilteredResults(IEnumerable<Person> people, string ModelProperty, string Value) method should create an expression that is roughly equivalent to people.Where(p => p.Locations.Any(pl => pl.Name == Value);
I have this working if ModelProperty is a string, i.e. people.Where(p => p.Name == Value) looks like this:
string[] modelPropertyParts = ModelProperty.Split('.');
var prop = typeof(Person).GetProperty(modelPropertyParts[0]);
var sourceParam = Expression.Parameter(typeof(Person), "person");
var expression = Expression.Equal(Expression.PropertyOrField(sourceParam, modelPropertyParts[0]), Expression.Constant(option.Name));
var whereSelector = Expression.Lambda<Func<Person, bool>>(orExp, sourceParam);
return people.Where(whereSelector.Compile());
Here's what I have been playing around with for an IEnumerable type, but I just can't get the inner Any, which seems correct, hooked into the outer Where:
/*i.e. modelPropertyParts[0] = Locations & modelPropertyParts[1] = Name */
string[] modelPropertyParts = ModelProperty.Split('.');
var interiorProperty = prop.PropertyType.GetGenericArguments()[0];
var interiorParameter = Expression.Parameter(interiorProperty, "personlocation");
var interiorField = Expression.PropertyOrField(interiorParameter, modelPropertyParts[1]);
var interiorExpression = Expression.Equal(interiorField, Expression.Constant(Value));
var innerLambda = Expression.Lambda<Func<PersonLocation, bool>>(interiorExpression, interiorParameter);
var outerParameter = Expression.Parameter(typeof(Person), "person");
var outerField = Expression.PropertyOrField(outerParameter, modelPropertyParts[0]);
var outerExpression = ??
var outerLambda == ??
return people.Where(outerLambda.Compile());
The problem is that System.Linq.Enumerable.Any is a static extension method.
Your outerExpression must reference System.Linq.Enumerable.Any(IEnumerable<T>, Func<T, bool>):
var outerExpression = Expression.Call(
typeof(System.Linq.Enumerable),
"Any",
new Type[] { outerField.Type, innerLambda.Type },
outerField, innerLambda);
Take a look at these links for more information:
MSDN Expression.Call(Type, String, Type[], params Expression[])
Some helpful, similar examples.