How to unpack an expression tree and check for null values - c#

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);
}
}
}

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);
}
}

Automapper - Make IncludeMembers() ignore null

IncludeMembers() will always map from first match even if object is null.
Let's say we have the following source models:
public class Item
{
public MovieMetadata MovieMetadata { get; set; }
public BookMetadata BookMetadata { get; set; }
}
public class MovieMetadata
{
public string Title { get; set; }
}
public class BookMetadata
{
public string Title { get; set; }
}
Destination model:
public class ItemDetail
{
public string Title { get; set; }
}
Mapping profile:
public class ItemProfile : Profile
{
public ItemProfile()
{
CreateMap<Item, ItemDetail>()
.IncludeMembers(
src => src.BookMetadata,
src => src.MovieMetadata);
CreateMap<BookMetadata, ItemDetail>();
CreateMap<MovieMetadata, ItemDetail>();
}
}
And Program class with instantiation and testing logic:
public class Program
{
public static void Main()
{
//create mapper
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddMaps(typeof(ItemProfile));
});
var mapper = configuration.CreateMapper();
//check if configuration valid
mapper.ConfigurationProvider.AssertConfigurationIsValid();
Console.WriteLine("Mapper configuration is valid");
//try map book metadata
var bookItem = new Item
{
BookMetadata = new BookMetadata()
{
Title = "book"
},
MovieMetadata = null
};
var bookItemDetail = mapper.Map<ItemDetail>(bookItem);
bool isBookCorrectlyMapped = bookItem.BookMetadata.Title == bookItemDetail.Title;
Console.WriteLine($"Book mapped correctly: {isBookCorrectlyMapped}");
//try map movie metadata
var movieItem = new Item
{
BookMetadata = null,
MovieMetadata = new MovieMetadata()
{
Title = "movie"
}
};
var movieItemDetail = mapper.Map<ItemDetail>(movieItem);
bool isMovieCorrectlyMapped = movieItem.MovieMetadata.Title == movieItemDetail.Title;
Console.WriteLine($"Movie mapped correctly: {isMovieCorrectlyMapped}");
}
}
This is the output we will see:
Mapper configuration is valid
Book mapped correctly: True
Movie mapped correctly: False
We see that mapping for item with BookMetadata succeeded, but for MovieMetadata failed. Need to make updates so this test succeeds.
Suppose the reason it's failing is because of order of items in IncludeMembers():
.IncludeMembers(
src => src.BookMetadata,
src => src.MovieMetadata)
Here we have src.BookMetadata first and src.MovieMetadata second. During mapping it will find Title field in src.BookMetadata and will always use this value even if src.BookMetadata is null.
Is there any way to skip src.BookMetadata if it is null and use next src.MovieMetadata instead? Or probably need to use something else instead of IncludeMembers()?
Here is similar issue: https://github.com/AutoMapper/AutoMapper/issues/3204
Code above you can find here to quickly reproduce issue: https://dotnetfiddle.net/9YLGR6
Found possible solutions.
Option 1. IValueResolver
We can create custom value resolver. This solution is good for small amount of fields that we need to map from child objects. As any field will require specific value resolver.
Let's create TitleResolver:
public class TitleResolver : IValueResolver<Item, ItemDetail, string>
{
public string Resolve(Item source, ItemDetail destination, string destMember, ResolutionContext context)
{
if (source != null)
{
if (source.BookMetadata != null)
{
return source.BookMetadata.Title;
}
if (source.MovieMetadata != null)
{
return source.MovieMetadata.Title;
}
}
return null;
}
}
And update ItemProfile:
public class ItemProfile : Profile
{
public ItemProfile()
{
CreateMap<Item, ItemDetail>()
.ForMember(dest => dest.Title, opt => opt.MapFrom<TitleResolver>());
}
}
Link to whole code sample: https://dotnetfiddle.net/6pfKYh
Option 2. BeforeMap()/AfterMap()
In case if our child objects have more than one field to be mapped to destination object it may be a good idea to use BeforeMap() or AfterMap() methods.
In that case ItemProfile will be updated to:
public class ItemProfile : Profile
{
public ItemProfile()
{
CreateMap<Item, ItemDetail>(MemberList.None)
.AfterMap((src, dest, ctx) =>
{
if (src.BookMetadata != null)
{
ctx.Mapper.Map(src.BookMetadata, dest);
}
else if (src.MovieMetadata != null)
{
ctx.Mapper.Map(src.MovieMetadata, dest);
}
});
CreateMap<BookMetadata, ItemDetail>();
CreateMap<MovieMetadata, ItemDetail>();
}
}
Link to whole code sample: https://dotnetfiddle.net/ny1yRU

How to trim all strings from a complex object returned with dapper

I am working with a legacy database, within this database, the data get assigned the maximum length of the column. if the string data is shorter, it will automaticly fill in whitespaces at the end.
What i'm trying to do is trim all these ending whitespaces with every query i do.
i figured one of the better ways would be making an extension method for dapper query using reflection.
But i can't seem to get it to work.
parent entity:
public class Person: BaseEntity
{
public Identification Identification { get; set; }
public Address Address { get; set; }
public Contact Contact { get; set; }
public Family Family { get; set; }
public ICollection<Payment> Payments { get; set; }
}
example of child entity:
public class Address: BaseEntity
{
public string Street { get; set; }
public int Number { get; set; }
public string BoxNumber { get; set; }
public int ZipCode { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
Now i do my join query like this:
var result = _db.QueryTrim<dynamic>(sql, new { userid = id })
.Select(p => new Person()
{
Identification = IdentificationMapper.MapToIdentificationEntity(p),
Address = AddressMapper.MapToAddressEntity(p)}).First();
i am trying to implement something like this but i can't get it to work with query
public static class DapperExtensions {
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) {
var dapperResult = SqlMapper.Query<T>(cnn, sql, param, transaction, buffered, commandTimeout, commandType);
var result = TrimStrings(dapperResult.ToList());
return result;
}
static IEnumerable<T> TrimStrings<T>(IList<T> objects) {
//todo: create an Attribute that can designate that a property shouldn't be trimmed if we need it
var publicInstanceStringProperties = typeof (T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.PropertyType == typeof (string) && x.CanRead && x.CanWrite);
foreach (var prop in publicInstanceStringProperties) {
foreach (var obj in objects) {
var value = (string) prop.GetValue(obj);
var trimmedValue = value.SafeTrim();
prop.SetValue(obj, trimmedValue);
}
}
return objects;
}
static string SafeTrim(this string source) {
if (source == null) {
return null;
}
return source.Trim();
}
}
In the end i want to trim all string that come out of the database. At the moment i'm tunnelvisioning on the dapperextension, but if there is any better way. Please let me know.
If you are using Dapper version 1.50.2 you can do it in a simpler way.
Create a TypeHandler like the one bellow:
public class TrimmedStringHandler : SqlMapper.TypeHandler<string>
{
public override string Parse(object value)
{
string result = (value as string)?.Trim();
return result;
}
public override void SetValue(IDbDataParameter parameter, string value)
{
parameter.Value = value;
}
}
On the program initialization you must call the AddTypeHandler method of the SqlMapper class like bellow:
SqlMapper.AddTypeHandler(new TrimmedStringHandler());
If you do this, every string that come from database will be trimmed.

Using lambda expression from a child class

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)

Get unique ID records from a List<T> of a collection

I got the following piece of code:
public class Collect
{
public string name{ get; set; }
public int id { get; set; }
public DateTime registerDate { get; set; }
}
public class ControllingMyList
{
public void prepareList()
{
List<Collect> list = new List<Collect>();
list= loadList();
//Rest of the opperations
}
}
Considering that my loadList method returns for me many duplicated records (id variable) I want to get only one record by ID.
The Distinct() function seems to be a good solution but if I remember correctly, Distinct() filters all the members of the object so just because of a second of difference from "registerDate" variable is considered a criteria to make it distinct, even if its with the same ID.
var list= loadList();
list = list
.GroupBy(i => i.id)
.Select(g => g.First())
.ToList();
You have several options:
Use DistinctBy() extension method from the MoreLinq project
Use the Distinct() overload that accepts a custom equality comparer, and implement a custom comparer
Use Linq GroupBy( x=> x.id) and then take the first item of each group.
Pass in a comparer that uses the id:
public class idCompare : IEqualityComparer<Collect>
{
public bool Equals(Collect x, Collect y)
{
return Equals(x.id, y.id);
}
public int GetHashCode(Collect obj)
{
return obj.id.GetHashCode();
}
}
....
list.Distinct(new idCompare());
static List GetUniques(IEnumerable collection, string attribute) where T : Entity
{
Dictionary<string, bool> tempkvp = new Dictionary<string, bool>();
List<T> uniques = new List<T>();
List<string> columns = collection.FirstOrDefault().GetType().GetProperties().ToList().ConvertAll<string>(x => x.Name.ToLower());
var property = attribute != null && collection.Count() > 0 && columns.Contains(attribute.ToLower()) ? ViewModelHelpers.GetProperty(collection.FirstOrDefault(), attribute) : null;
if (property != null)
{
foreach (T obj in collection)
{
string value = property.GetValue(obj, null).ToString();
if (!(tempkvp.ContainsKey(value)))
{
tempkvp.Add(value, true);
uniques.Add(obj);
}
}
}
return uniques;
}
Implement IEquatable<T> and override Equals and GetHashCode. You can have those take into account only id.
using System;
public class Collect : IEquatable<Collect>
{
public string name{ get; set; }
public int id { get; set; }
public DateTime registerDate { get; set; }
public bool Equals(Collect other)
{
if(other == null)
{
return false;
}
return this.id == other.id;
}
}

Categories