Using lambda expression from a child class - c#

I have following situation:
public class BaseClass
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
public class Product: BaseClass
{
/* Some methods*/
}
public class Country: BaseClass
{
/* Some methods*/
}
public class MyCustomClass
{
public Product Product { get; set; }
public Country Country { get; set; }
}
I want to create a list of expressions which I would later use to query my MyCustomClass, example
var listOfExpressions= new List<Expression<Func<MyCustomClass, bool>>>();
if (x == 1)
listOfExpressions.Add(x => x.Product.Field1 == "Fruit")
/*.
.
.*/
if (y == 2)
listOfExpressions.Add(x => x.Country.Field2 == "Western")
}
Now this looks really ugly with these bunch of Ifs and I would prefer to have a method in BaseClass for which you would pass int ({1,2,3}) and it would return me the expression. The problem is that I need to have expression of type <MyCustomClass, bool> but in my BaseClass I can't directly get that, is there an way I could acheive this without complicating code too much?
I am thinking about something like this:
public class BaseClass
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
protected Expression<Func<BaseClass, bool>> GetExpression(int key, string value)
{
switch (key)
{
case 1:
return x => x.Field1 == value;
case 2:
return x => x.Field2 == value;
case 3:
return x => x.Field3 == value;
}
}
By now I am stuck of how to get Expression<Func<BaseClass, bool>> to be used in Expression<Func<MyCustomClass, bool>>.
EDIT: The purpose of the expression:
I want to constuct a list of expressions to later use it to query database using Entity framework core. Example:
var query = DbContext.MyCustomClass.AsQueryable();
foreach (var expression in listOfExpressions)
{
query = query.Where(expression);
}

You can build your Expression on you own. Just add another parameter to GetExpression:
public static Expression<Func<MyCustomClass, bool>> GetExpression(
string derrivedName, // "Product" or "Country"
int fieldId,
string value)
{
var x = Expression.Parameter(typeof(MyCustomClass), "x");
var lambda = Expression.Lambda<Func<MyCustomClass, bool>>(
Expression.Equal(
Expression.Constant(value),
Expression.PropertyOrField(
Expression.PropertyOrField(x, derrivedName),
$"Field{fieldId}")
),
x);
return lambda;
}
Now you can use it like:
var exp3 = GetExpression("Product", 1, "Fruit");
This line will create an expression x => ("Fruit" == x.Product.Field1)

Related

Using LINQ to select EF properties in DBSet with specific Attributes

Is there a way to perform a LINQ query on Entity Framework DBSets and only return the properties that have a specific custom attribute?
My goal is to perform a query and return only the properties/columns that I need for an export.
I also want to make this an extension for IEnumerable as I have many separate EF Classes that will use this export attribute.
This is how I am visualizing it:
public class Person
{
[Export]
public string FirstName { get; set; }
[Export]
public string LastName { get; set; }
[Export]
public string Address { get; set; }
[NeverExport]
public string SocialSecurityNumber { get; set; }
}
public void main()
{
var people = PersonRepository.GetAll();
var exportResults = people.GetByAttribute(ExportAttribute);
{
public static IEnumerable<object> GetByAttribute(this IEnumerable<object> list, Attribute attribute)
{
return list
.Select(x =>
// Select properties with { attribute } (ie. [Export])
);
}
I've made a very basic example for your problem. In short, you want to get all Properties, where a specific Attribute is present. You can achive this via reflection. First, you want all properties of a specific type, then only the properties, where a specific attribute is present.
Here is my example code:
using System;
using System.Linq;
using System.Reflection;
namespace Sandbox
{
public class Program
{
public static void Main(string[] args)
{
var attrs = typeof(User).GetProperties().Where(x => x.GetCustomAttributes().Any(y => y.GetType() == typeof(Custom)));
var users = new User[]
{
new User() { ID = 1, Name = "John", Lastname = "Doe" },
new User() { ID = 2, Name = "Jane", Lastname = "Doe" }
};
foreach(var u in users)
{
foreach(var attr in attrs)
{
Console.WriteLine(typeof(User).GetProperty(attr.Name).GetValue(u, null));
}
}
}
}
public class User
{
public string Name { get; set; }
[Custom]
public int ID { get; set; }
[Custom]
public string Lastname { get; set; }
}
public class Custom : System.Attribute
{
}
}
The following code projects any IQueryable to IQueryable<ExpandoObject> and you can use it for exporting data. Additionally code caches projection expression for reusing later in application.
Usage is simple:
var exportResult = anyQuery.ProjectForExport().ToList();
And implementation:
[AttributeUsage(AttributeTargets.Property)]
public class ExportAttribute : Attribute
{
}
public static class ExportTools
{
private static readonly ConstructorInfo _expandoObjectConstructor =
typeof(ExpandoObject).GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException();
private static readonly MethodInfo _expandoAddMethodInfo = typeof(IDictionary<string, object>).GetTypeInfo()
.GetRuntimeMethods()
.Single(mi => mi.Name == nameof(IDictionary<string, object>.Add) && mi.GetParameters().Length == 2);
public static class ProjectionCache<T>
{
public static Expression<Func<T, ExpandoObject>> ProjectionExpression { get; } = BuildProjection();
private static Expression<Func<T, ExpandoObject>> BuildProjection()
{
var param = Expression.Parameter(typeof(T), "e");
var properties = typeof(T).GetProperties()
.Where(p => p.GetCustomAttributes().Any(a => a is ExportAttribute)).ToList();
if (properties.Count == 0)
throw new InvalidOperationException($"Type '{typeof(T).Name}' has no defined Export properties.");
var expr = (Expression)Expression.ListInit(
Expression.New(_expandoObjectConstructor),
properties.Select(p =>
{
var readerExpr = Expression.MakeMemberAccess(param, p);
return Expression.ElementInit(_expandoAddMethodInfo, Expression.Constant(p.Name), readerExpr);
}));
var lambda = Expression.Lambda<Func<T, ExpandoObject>>(expr, param);
return lambda;
}
}
public static IQueryable<ExpandoObject> ProjectForExport<T>(this IQueryable<T> query)
{
return query.Select(ProjectionCache<T>.ProjectionExpression);
}
}

pass property to be used in lambda expression

I am having trouble with generics. Using examples from here, I want to pass a property for use in EF Core. I have a feeling I am overcomplicating things. Note that I would like to stick with lamba expressions instead of passing something like nameof (which I cannot use in 4.0).
class Foo
{
public string A { get; set; } // some unique string in DbSet for entity Person
public string B { get; set; } // some unique string in DbSet for entity Company
public int GetId<TEntity>(string match, Expression<Func<TEntity, string>> fieldToExamine, Expression<Func<TEntity, int>> fieldWithId, System.Data.Entity.DbContext context)
where TEntity : class
{
int retval = -1;
if (!string.IsNullOrEmpty(match))
{
var expr = (MemberExpression)fieldToExamine.Body;
var prop = (PropertyInfo)expr.Member;
var expr2 = (MemberExpression)fieldWithId.Body;
var idField = (PropertyInfo)expr2.Member;
// this works because everything is explicit
var entity = context.Set<Person>().SingleOrDefault(p => p.PersonName.Equals(match));
// ... but I want that to be generic
// how to reference the property to be evaluated?
//var entity = context.Set<TEntity>().SingleOrDefault(p => ...); // <- HELP?!
if (entity != null)
{
retval = (int)entity.GetType().GetProperty(idField.Name).GetValue(entity, null);
}
}
return retval;
}
}
static void Main(string[] args)
{
// .. omitted database stuff ..
// (for Person) what I want is to match the Name field (here: p.Name) of the Person entity to the Foo property value (here: f.A) and return the database id stored in p.PersonId
int x = f.GetId<Person>(f.A, p => p.PersonName, p => p.PersonId, context);
// same for Company, but different values and fields
int y = f.GetId<Company>(f.B, p => p.CompanyName, p => p.CompanyId, context);
}
Person class and Company class will need to implement either abstract class or interface.
public abstract class BaseEntity
{
public abstract int Id { get; set; }
public abstract string Name { get; set; }
}
public class Person : BaseEntity
{
public int PersonId { get; set; }
public override string Name { get; set; }
public override int Id
{
get { return PersonId; }
set { PersonId = value; }
}
}
public class Company : BaseEntity
{
public int CompanyId { get; set; }
public override string Name { get; set; }
public override int Id
{
get { return CompanyId; }
set { CompanyId = value; }
}
}
Then you can pass p.Name and p.CompanyId.
var entity = context.Set<TEntity>().SingleOrDefault(p => p.Name.Equals(match));

Automate the validation process on properties using FluentValidation Library

Wondering is there a possibility to avoid writing rules for every fields that needs to get validated, for an example all the properties must be validated except Remarks. And I was thinking to avoid writing RuleFor for every property.
public class CustomerDto
{
public int CustomerId { get; set; } //mandatory
public string CustomerName { get; set; } //mandatory
public DateTime DateOfBirth { get; set; } //mandatory
public decimal Salary { get; set; } //mandatory
public string Remarks { get; set; } //optional
}
public class CustomerValidator : AbstractValidator<CustomerDto>
{
public CustomerValidator()
{
RuleFor(x => x.CustomerId).NotEmpty();
RuleFor(x => x.CustomerName).NotEmpty();
RuleFor(x => x.DateOfBirth).NotEmpty();
RuleFor(x => x.Salary).NotEmpty();
}
}
Here you go:
public class Validator<T> : AbstractValidator<T>
{
public Validator(Func<PropertyInfo, bool> filter) {
foreach (var propertyInfo in typeof(T)
.GetProperties()
.Where(filter)) {
var expression = CreateExpression(propertyInfo);
RuleFor(expression).NotEmpty();
}
}
private Expression<Func<T, object>> CreateExpression(PropertyInfo propertyInfo) {
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyInfo);
var conversion = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<T, object>>(conversion, parameter);
return lambda;
}
}
And it can be used as such:
private static void ConfigAndTestFluent()
{
CustomerDto customer = new CustomerDto();
Validator<CustomerDto> validator = new Validator<CustomerDto>(x=>x.Name!="Remarks"); //we specify here the matching filter for properties
ValidationResult results = validator.Validate(customer);
}

How to unpack an expression tree and check for null values

With reference to the 2 classes below, I am regularly writing LINQ statements like this..
using (var db = new DBContext())
{
var result = db.Countries
.Select(c => new
{
c.Name,
c.Leader != null ? c.Leader.Title : String.Empty,
c.Leader != null ? c.Leader.Firstname : String.Empty,
c.Leader != null ? c.Leader.Lastname : String.Empty
});
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public Leader Leader { get; set; }
}
public class Leader
{
public string Title { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
}
My issues that I am having to constantly repeat my null checks on children navigation properties and I was wondering if there is a way I can use some kind of expression tree to extract the property values dynamically whilst checking for null values and if they didn't exist send back an empty string, something like the method below..
public class Country
{
// Properties //
public string SafeGet(Expression<Func<Country, string>> fnc)
{
// Unpack fnc and check for null on each property?????
}
}
Usage:
using (var db = new DBContext())
{
var result = db.Countries
.Select(c => new
{
c.Name,
c.SafeGet(l => l.Leader.Title),
c.SafeGet(l => l.Leader.Firstname),
c.SafeGet(l => l.Leader.Lastname)
});
}
If someone could provide a basic example that would be great as I don't have a whole lot of experience with expression tree's other than creating them.
Thanks.
Update -> would something like the following work?
public string GetSafe(Expression<Func<Country, string>> fnc)
{
var result = fnc.Compile().Invoke(this);
return result ?? string.Empty;
}
I see no need for an expression. I would simply go for an extension-method like
public static class ModelExtensions
{
// special case for string, because default(string) != string.empty
public static string SafeGet<T>(this T obj, Func<T, string> selector)
{
try {
return selector(obj) ?? string.Empty;
}
catch(Exception){
return string.Empty;
}
}
}
It works for all classes and you could implement further for other datatypes. The usage is the same as yours.
I think that you want something like this:
public static class ModelExtensions
{
public static TResult SafeGet<TSource, TResult>(this TSource obj, System.Func<TSource, TResult> selector) where TResult : class
{
try
{
return selector(obj) ?? default(TResult);
}
catch(System.NullReferenceException e)
{
return default(TResult);
}
}
}

How can I select the property for OrderBy/Where at runtime (not with a string parameter)?

Updated example to show more general usage.
I have an entity to allow user-provided localization:
public class ResourceValue
{
public int ResourceValueId { get; set; }
public string EnglishValue { get; set; }
public string FrenchValue { get; set; }
public string SpanishValue { get; set; }
etc...
}
Used on many other entities like this:
public class SomeEntity
{
public int Id { get; set; }
public virtual ResourceValue Name { get; set; }
public virtual ResourceValue ShortDescription { get; set; }
public virtual ResourceValue LongDescription { get; set; }
etc...
}
I would like to do something like this:
return context.SomeEntities.OrderBy(x => x.Name);
And have that work as if I had done this:
return context.SomeEntities.OrderBy(x => x.Name.FrenchValue);
based on the CurrentUICulture being "fr-CA".
I have been trying some things based on Marc Gravell's answer here: https://stackoverflow.com/a/1231941 but haven't been able to get quite what I want.
Update - This is pretty close, but I would rather have it be named just "OrderBy" so the end-coder can use it without special consideration:
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
{
return ApplyLocalizedOrder(source, keySelector, "OrderBy");
}
public static IOrderedQueryable<TSource> ApplyLocalizedOrder<TSource, TKey>(IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, string methodName)
{
ParameterExpression arg = keySelector.Parameters[0];
Expression expr = Expression.PropertyOrField(keySelector.Body, GetCurrentCulture());
LambdaExpression lambda = Expression.Lambda<Func<TSource, string>>(expr, arg);
return (IOrderedQueryable<TSource>)typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(TSource), expr.Type)
.Invoke(null, new object[] { source, lambda });
}
While creating lambda expressions dynamically is cool you can achieve your result in a much simpler way just by creating a method that will apply the sort on top of your query. The method would just look like this:
private static IQueryable<SomeEntity> OrderByName(IQueryable<SomeEntity> source, string culture)
{
if (culture == "fr-CA")
{
return source.OrderBy(x => x.Name.FrenchValue);
}
return source.OrderBy(x => x.Name.EnglishValue);
}
And then you would just use it as follows:
OrderByName(context.SomeEntities, "en-US")
Here is the entire example:
public class MyCtx1 : DbContext
{
public DbSet<SomeEntity> SomeEntities { get; set; }
public DbSet<ResourceValue> ResourceValues { get; set; }
}
public class SomeEntity
{
public int Id { get; set; }
public virtual ResourceValue Name { get; set; }
}
public class ResourceValue
{
public int ResourceValueId { get; set; }
public string EnglishValue { get; set; }
public string FrenchValue { get; set; }
}
class Program
{
private static IQueryable<SomeEntity> OrderByName(IQueryable<SomeEntity> source, string culture)
{
if (culture == "fr-CA")
{
return source.OrderBy(x => x.Name.FrenchValue);
}
return source.OrderBy(x => x.Name.EnglishValue);
}
static void Main(string[] args)
{
using (var context = new MyCtx1())
{
if (!context.SomeEntities.Any())
{
context.SomeEntities.Add(
new SomeEntity()
{
Name = new ResourceValue()
{
EnglishValue = "abc - en",
FrenchValue = "xyz - fr"
}
});
context.SomeEntities.Add(
new SomeEntity()
{
Name = new ResourceValue()
{
EnglishValue = "xyz - en",
FrenchValue = "abc - fr"
}
});
context.SaveChanges();
}
Console.WriteLine("Ordered by english name");
DisplayResults(OrderByName(context.SomeEntities, "en-US"));
Console.WriteLine("Ordered by french name");
DisplayResults(OrderByName(context.SomeEntities, "fr-CA"));
}
}
private static void DisplayResults(IQueryable<SomeEntity> q)
{
foreach (var e in q)
{
Console.WriteLine(e.Id);
}
}
And the result:
Ordered by english name
1
2
Ordered by french name
2
1
Press any key to continue . . .
context.SomeEntities.Select(v => v.Name.FrenchName).OrderBy(x => x);
But even better than that in your get for name, return the current culture or a certain culture or whatever your code is calling for, no reason to do it in the Linq query when your class could do it. when it done in the class it is better anyway because then anywhere you call the code it is returning the correct culture.

Categories