How to create dynamic NRules - c#

I am getting this exception:
variable 'e' of type 'MyClass' referenced from scope '', but it is not defined
I want to create dynamic rules with nested property. When I create static rules it works fine, but not in dynamic mode. Also, I want to use expression for dynamic rules using these lines of code if possible:
PatternBuilder customerPattern = builder.LeftHandSide().Pattern(typeof(Customer), "customer");
Expression<Func<Customer, bool>> customerCondition = customer => customer.Name == "John Do";
customerPattern.Condition(customerCondition);
I tried the code below to create and execute rules dynamically, but I am getting an exception. Why?
class Program
{
static void Main(string[] args)
{
try
{
CustomRuleRepository repository = new CustomRuleRepository();
List<RuleEngineEntity> rules = new List<RuleEngineEntity>();
rules.Add(new RuleEngineEntity { FieldName = "Age", Name = "CustomerCheck", Value = 20 });
repository.LoadRules(rules);
//Compile rules
var factory = repository.Compile();
//Create a working session
var session = factory.CreateSession();
RuleEngineRequestModel ruleEngineRequestModel = new RuleEngineRequestModel { ruleList = rules, customerData = new Customer { Name = "A", Age = 24 } };
session.Insert(ruleEngineRequestModel);
var IspassedorNot = session.Fire();
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
}
}
public class RuleEngineRequestModel
{
public List<RuleEngineEntity> ruleList { get; set; }
public Customer customerData { get; set; }
}
public class RuleEngineEntity
{
public string Name { get; set; }
public int Value { get; set; }
public string Operator { get; set; }
public string FieldName { get; set; }
}
public class Customer
{
public string Name { get; set; }
public int Age { get; set; }
}
public class CustomRuleRepository : IRuleRepository
{
private readonly IRuleSet _ruleSet = new RuleSet("customerRule");
public IEnumerable<IRuleSet> GetRuleSets()
{
return new[] {_ruleSet};
}
public void LoadRules(List<RuleEngineEntity> list)
{
_ruleSet.Add(
BuildRule(list)
);
}
public List<IRuleDefinition> BuildRule(List<RuleEngineEntity> list)
{
NRules.RuleModel.Builders.RuleBuilder builder = null;
List<IRuleDefinition> rulesList = new List<IRuleDefinition>();
builder = new NRules.RuleModel.Builders.RuleBuilder();
builder.Name("CustomerDetail");
ParameterExpression customerParameter = null;
LambdaExpression customerCondition = null;
PatternBuilder customerPattern = null;
try
{
var orGroup = builder.LeftHandSide().Group(GroupType.Or);
foreach (var item in list)
{
var andGroup = orGroup.Group(GroupType.And);
customerPattern = andGroup.Pattern(typeof(RuleEngineRequestModel), item.Name);
customerParameter = customerPattern.Declaration.ToParameterExpression();
customerCondition =
Expression.Lambda(
Expression.GreaterThan(CreateParameterExpression(typeof(RuleEngineRequestModel), "customerData", typeof(Customer), item.FieldName),
Expression.Constant(item.Value)), customerParameter);
customerPattern.Condition(customerCondition);
}
Expression<Action<IContext>> action =
(ctx) => Console.WriteLine("Action triggered");
builder.RightHandSide().Action(action);
rulesList.Add(builder.Build());
}
catch (Exception e)
{
}
return rulesList;
}
public Expression CreateParameterExpression(Type type, string propertyName, Type type2, string propertyName2)
{
ParameterExpression pe = Expression.Parameter(type, "e");
Expression left = Expression.Property(pe, type.GetProperty(propertyName));
return Expression.Property(left, type2.GetProperty(propertyName2));
}
}

You have a CreateParameterExpression method, that's supposed to create a property access expression. But in that method you are creating a parameter expression there with a name "e", which does not correspond to any pattern that you defined in your rule. This causes the "variable not found" exception.
You simply need one MemberExpression to access the customerData property, and then another one to access the nested property, configured in the RuleEngineEntity.
Here is an updated BuildRule method that implements the necessary expression tree.
public List<IRuleDefinition> BuildRule(List<RuleEngineEntity> list)
{
var builder = new NRules.RuleModel.Builders.RuleBuilder();
builder.Name("CustomerDetail");
var orGroup = builder.LeftHandSide().Group(GroupType.Or);
foreach (var item in list)
{
var andGroup = orGroup.Group(GroupType.And);
var modelPattern = andGroup.Pattern(typeof(RuleEngineRequestModel), item.Name);
var modelParameter = modelPattern.Declaration.ToParameterExpression();
var customerData = Expression.Property(modelParameter, nameof(RuleEngineRequestModel.customerData));
var customerCondition = Expression.Lambda(
Expression.GreaterThan(
Expression.Property(customerData, item.FieldName),
Expression.Constant(item.Value)),
modelParameter);
modelPattern.Condition(customerCondition);
}
Expression<Action<IContext>> action =
ctx => Console.WriteLine("Action triggered");
builder.RightHandSide().Action(action);
var rule = builder.Build();
return new List<IRuleDefinition> {rule};
}

Related

C#- Filter data based on child class or using string query

Hi I have a scenario to filter the data based sub-object field please help me. From controller as query I pass Expression String.
class MasterDocument
{
private Id;
public ICollection<SubDocument> documents { get; set; }
}
class SubDocument
{
private Id;
public int Age { get; set; }
}
var filterQuery = "documents.Age == 25";
var filteredResult = MasterDocument.Where(filterQuery).ToList();
to filter the Data 
how to create Expression from string to filter data from Substructure.
Well, that's quite complicated topic, but i will first give code example and later focus on caveats:
I would follow approach and define it as another extension method:
using System.Linq.Expressions;
namespace ConsoleApp2;
public static class WhereExtensions
{
public static IEnumerable<T> Where<T>(
this IEnumerable<T> collection,
string filterExpression)
{
// Most probably you'd like to have here more sophisticated validations.
var itemsToCompare = filterExpression.Split("==")
.Select(x => x.Trim())
.ToArray();
if (itemsToCompare.Length != 2)
{
throw new InvalidOperationException();
}
var source = Expression.Parameter(typeof(T));
var property = itemsToCompare[0];
var valueToCompareAgainst = itemsToCompare[1];
var memberExpr = source.GetMemberExpression(property);
var comparisonExpr = Expression.Equal(
Expression.Call(memberExpr, typeof(object).GetMethod("ToString")),
Expression.Constant(valueToCompareAgainst)
);
var predicate = Expression.Lambda<Func<T, bool>>(comparisonExpr, source);
return collection.Where(predicate.Compile());
}
public static MemberExpression GetMemberExpression(
this ParameterExpression parameter,
string memberExpression)
{
var properties = memberExpression.Split('.');
if (!properties.Any())
{
throw new InvalidOperationException();
}
var memberExpr = Expression.PropertyOrField(parameter, properties[0]);
foreach (var property in properties.Skip(1))
{
memberExpr = Expression.PropertyOrField(memberExpr, property);
}
return memberExpr;
}
}
and the usage would be:
using ConsoleApp2;
var example = new[]
{
new TestClass() { Id = 1, Description = "a" },
new TestClass() { Id = 2, Description = "a" },
new TestClass() { Id = 3, Description = "b" },
new TestClass() { Id = 4, Description = "b" },
};
var result1 = example.Where("Id == 1").ToList();
var result2 = example.Where("Description == b").ToList();
Console.WriteLine("Items with Id == 1");
result1.ForEach(x => Console.WriteLine($"Id: {x.Id} , Descr: {x.Description}"));
Console.WriteLine("Items with Description == b");
result2.ForEach(x => Console.WriteLine($"Id: {x.Id} , Descr: {x.Description}"));
class TestClass
{
public int Id { get; set; }
public string Description { get; set; }
}
This codes returns:
NOW, THE CAVEATS
It's very tricky to cast value to compare against to an arbitrary type T, that's why i reversed the problem, and I call "ToString" on whetever member we want to compare
Expression.Call(memberExpr, typeof(object).GetMethod("ToString"))
But this also could have it's own issues, as often "ToString" returns default tpye name. But works well with integers and simple value types.

How to create an expression getting a property dynamically using LinqExpression in c#?

I am trying to create a linq expression getting dynamically a property from a Student object based on a SearchFilter object parameter.
The problem is that Expression.Property(expr, string) asks for a string for the property name. And I cannot retrieve the string from the ParameterExpression. I tried this but I am stuck... The cast as a (string) off course does not work. Any clue on how to proceed instead ?
Check the code below:
using System;
using System.Linq.Expressions;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var student = new Student
{
FirstName = "John",
Address = new Address
{
City = "New York",
ZipCode = 10005
}
};
var filter = new SearchFilter
{
Rule = "ZipCode"
};
var studentParamExpr = Expression.Parameter(typeof(Student), "s");
var filterParamExpr = Expression.Parameter(typeof(SearchFilter), "filter");
var lambdaExpr = Expression.Lambda(
CreateStudentAddressRuleAccessor(studentParamExpr, filterParamExpr),
new ParameterExpression[] { studentParamExpr, filterParamExpr });
var expression = lambdaExpr.Compile().DynamicInvoke(student, filter);
}
public static Expression CreateStudentAddressRuleAccessor(ParameterExpression student, ParameterExpression filter)
{
var ruleExpr = Expression.Property(filter, "Rule");
string rule = (string)ruleExpr;
var resultExpr = Expression.Property(Expression.Property(student, "Address"), rule);
return resultExpr;
}
}
public class Student
{
public string FirstName { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
public int ZipCode { get; set; }
}
public class SearchFilter
{
public string Rule { get; set; }
}
}
Lambda body does not require all of their arguments to be expressions.
Remove the filter from the list of lambda params and turn the type of the filter arg to SearchFilter like this:
static void Main(string[] args)
{
...
var studentParamExpr = Expression.Parameter(typeof(Student), "s");
var lambdaExpr = Expression.Lambda(
CreateStudentAddressRuleAccessor(studentParamExpr, filter),
new ParameterExpression[] { studentParamExpr });
var expression = lambdaExpr.Compile().DynamicInvoke(student);
Console.WriteLine($"Lambda expression = {lambdaExpr}");
Console.WriteLine($"Expression = {expression}");
}
public static Expression CreateStudentAddressRuleAccessor(ParameterExpression student, SearchFilter filter)
{
var resultExpr = Expression.Property(Expression.Property(student, "Address"), filter.Rule);
return resultExpr;
}

Creating dynamic Expression Tree for selected search criteria

I have an Expression Tree to create a dynamic where clause based on the criteria a user selects on a checkbox.
Eg: - User wants to search for: "test"
User selects
1. Prop1
2. Prop2
for an Object
MyDBObject
The search query will look like
dbRecords.Where(r=> r.Prop1.Contains("test") || r.Prop2.Contains("test"))
The reason to use an Expression Tree is so that it can be used for any unknown number of properties of an unknown object.
I almost have it working, but I get Argument Expression is not valid
Also how does one initialize an empty boolean expression other than using
"something that evaluates to -- true/false" ?
I've only read about them for a few hours by now so maybe there's something I didn't see yet.
public static Expression<Func<T, bool>> CreatePredicateFromCrtieriaAndSearchTerm<T>(List<string> checkedCriteria, string searchTerm)
{
// sample checked records
checkedCriteria = new[]
{
new { Name = "Prop1", DisplayValue = "Checkbox value 1" },
new { Name = "Prop2", DisplayValue = "Checkbox value 2" }
}
.Select(x => x.Name).ToList();
var param = Expression.Parameter(typeof(T), "record");
Expression oneEqualsOne = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
// Creates (record => (1=1) AND ...)
Expression<Func<T, bool>> finalExpression = Expression.Lambda<Func<T, bool>>(oneEqualsOne, param);
Console.WriteLine(finalExpression);
try
{
// Iterate through properties, find selected props and create
// (record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") ... )
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
List<Expression> matchExpressions = new List<Expression>();
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
for (int j = 0; j < checkedCriteria.Count; j++)
{
if (prop.Name == checkedCriteria[j])
{
// add to where expression
Expression left = Expression.Property(param, prop.Name);
MethodInfo contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
Expression right = Expression.Constant(searchTerm, searchTerm.GetType());
Expression matchExpression = Expression.Call(left, contains, right);
matchExpressions.Add(matchExpression);
}
}
}
// Creates (1=0 OR ... OR ...)
Expression currentPredicateBody = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
foreach (var matchExpression in matchExpressions)
{
currentPredicateBody = Expression.MakeBinary(ExpressionType.OrElse, matchExpression, currentPredicateBody);
Console.WriteLine(currentPredicateBody);
}
// ( (1=0) || record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") )
if (matchExpressions.Count > 0)
{
oneEqualsOne = Expression.AndAlso(oneEqualsOne, currentPredicateBody);
Console.WriteLine(oneEqualsOne);
}
// Full expression:
// ( record => (1=1) AND ( (1=0) || record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") ))
finalExpression = Expression.Lambda<Func<T, bool>>(oneEqualsOne, new ParameterExpression[] { param });
Console.WriteLine(finalExpression);
}
catch (Exception ex)
{
throw new Exception(string.Format(#"Error occurred creating where predicate from checked criteria: {0}", ex.Message));
}
return finalExpression;
}
internal class MyDBObject
{
public int Id { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public string Prop4 { get; set; }
public string Prop5 { get; set; }
public string Prop11 { get; set; }
public string Prop12 { get; set; }
public string Prop13 { get; set; }
public string Prop14 { get; set; }
public string Prop15 { get; set; }
public string Prop21 { get; set; }
public string Prop22 { get; set; }
public string Prop23 { get; set; }
public string Prop24 { get; set; }
public string Prop25 { get; set; }
}
public static void Main(string[] args)
{
List<MyDBObject> dbRecords = new List<MyDBObject>
{
new MyDBObject { Id = 1, Prop2 = "O1_P2", Prop3 = "O1_P3", Prop12 = "O1_P12", Prop15 = "O1_P15", Prop24 = "O1_P24", Prop25 = "O1_P25" },
new MyDBObject { Id = 2, Prop15 = "O2_P15", Prop21 = "test", Prop22 = "O2_P22", Prop23 = "O2_P23", Prop24 = "O2_P24", Prop25 = "O2_P25" },
new MyDBObject { Id = 3, Prop21 = "O3_P21", Prop22 = "O3_P22", Prop23 = "O3_P23", Prop24 = "test", Prop25 = "O3_P25" }
};
try
{
var predicate = CreatePredicateFromCrtieriaAndSearchTerm<MyDBObject>(null, "test");
var query = dbRecords.AsQueryable().Provider.CreateQuery<MyObject>(predicate);
List<MyObject> results = query.ToList();
foreach (var rs in results)
{
Console.WriteLine("Id: " + rs.Id);
}
}
catch (Exception ex)
{
Console.WriteLine("Error->> " + ex.Message);
}
}
Try this code:
public static Expression<Func<T, bool>> CreatePredicate<T>(List<string> propsToSearch,
string valueToSearch)
{
var parameter = Expression.Parameter(typeof(T), "record");
// filtering is not required
if (!propsToSearch.Any() || string.IsNullOrEmpty(valueToSearch))
return Expression.Lambda<Func<T, bool>>(Expression.Constant(true), parameter);
var props = typeof(T).GetProperties()
.Select(p => p.Name)
.Intersect(propsToSearch.Distinct());
var containsMethod = typeof(string).GetMethod("Contains");
var body = props
.Select(p => Expression.PropertyOrField(parameter, p))
.Aggregate((Expression) Expression.Constant(false),
(c, n) => Expression.OrElse(c,
Expression.Call(n, containsMethod, Expression.Constant(valueToSearch)))
);
var lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
return lambda;
}
It return record => true if there is no properties to search or search patern is empty. QueryProvider can be smart enough to not generate sql where in this case.
Update: I created a demo (it's not working because of security restriction of dotNetFiddle, but localy works fine)

Convert the class as data table using extension method and lambda expression

I am trying to create a extension method to export the class as data-table, in this method I want to give facility to user to export the property with different name in datatable, suppose property name in class is "LoginName" but user want to export it as "Login" in data-table, also user can specify multiple properties to rename.
for example following is the class
public class UserInfo
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string LoginName { get; set; }
public int CompanyID { get; set; }
}
to export this class as data-table user will use extension method like this
UserInfo us = UserRep.GetUser("userID","Pass");
DataTable userDetails = null;
//Follwing is a pseudo code it could be different in possible manner
userDetails = us.ExportAsDataTable(u=> new {{u.LoginName,"Login"}, {u.CompanyID ,"Company"}});
//Or
userDetails = us.ExportAsDataTable(u=> new { Login = u.LoginName, Company = u.CompanyID});
following ExportAsDataTable metod I have created to do the functionality but unable to give correct expression to take the user input.
public static DataTable ExportAsDataTable<TSource, TProperty>(this TSource instance, Expression<Func<TSource, KeyValuePair<TProperty, string>>> renamePropertyMap)
{
DataTable dataTable = new DataTable();
//Doing export stuff here
return dataTable;
}
//Or
public static DataTable ConvertToDataTable<T>(this T instance, Expression<Func<T, object>> renamePropertyMap) where T : EntityBase
{
//Using this method I am able to get the new name of column from expression like this but not getting the original property name
string columnName = (renamePropertyMap.Body as NewExpression).Members[0].Name
/*note :- result in columnName is "Login" which is fine,
but I need to get orignal property name as well, that is
"LoginName", I am unable to get it from expression.*/
DataTable dataTable = new DataTable();
//Doing export stuff here
return dataTable;
}
I would do something like this instead:
public class FluentBuilder<T>
{
private readonly T _input;
private readonly Dictionary<string, string> _mappings = new Dictionary<string, string>();
public FluentBuilder(T input)
{
_input = input;
}
public FluentBuilder<T> Map(Expression<Func<T, object>> selector, string name)
{
MemberExpression member = selector.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.", selector));
var propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format("Expression '{0}' refers to a field, not a property.", selector));
_mappings.Add(propInfo.Name, name);
return this;
}
private string GetName(PropertyInfo prop)
{
string map;
if (_mappings.TryGetValue(prop.Name, out map))
return map;
return prop.Name;
}
public DataTable ToDataTable(string tableName = null)
{
var result = new DataTable(tableName);
foreach (var prop in _input.GetType().GetProperties())
{
result.Columns.Add(GetName(prop));
}
var values = _input.GetType().GetProperties().Select(x => x.GetMethod.Invoke(_input, new object[0])).ToArray();
result.Rows.Add(values);
return result;
}
}
public static class FluentBuilderExtensions
{
public static FluentBuilder<T> SetupWith<T>(this T input)
{
return new FluentBuilder<T>(input);
}
}
class Program
{
public class UserInfo
{
public string MailAddress { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
static void Main(string[] args)
{
var userInfo = new UserInfo()
{
MailAddress = "foo#bar.com",
Username = "foouser",
Password = "barpassword"
};
var dt = userInfo.SetupWith()
.Map(x => x.MailAddress, "address")
.Map(x => x.Username, "user")
.ToDataTable();
}
}
MoreLINQ already provides ToDatatable() to convert an IEnumerable result to a DataTable. The method is available as part of the full NuGet package or as a source package that you can add to your project.
To generate a DataTable from a subset of UserInfo properties, use a Select before calling ToDataTable(), eg :
var table = myUserInfos.Select(us=>new {Login=us.LoginName, Company=us.CompanyID})
.ToDataTable();
If you only have one item and want to convert it to a single-row DataTable:
That's a very strange request. Why do you want to do that instead of eg binding the single object to the UI controls?
You can wrap it in an array eg:
var table = new[]{theUser}.Select(us=>new {Login=us.LoginName, Company=us.CompanyID})
.ToDataTable();
I used DataAnnotations and Reflection.
You need to add reference of missing libraries from following. I tested this code and it is working.
using System;
using System.Data;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var user = new UserInfo();
var name = GetAttributeFrom<DisplayAttribute>(user, "LoginName").Name;
var dt = objToDataTable(user);
Console.ReadLine();
}
public static DataTable objToDataTable(UserInfo obj)
{
DataTable dt = new DataTable();
UserInfo objU = new UserInfo();
foreach (PropertyInfo info in typeof(UserInfo).GetProperties())
{
dt.Columns.Add(GetAttributeFrom<DisplayAttribute>(objU, info.Name).Name);
}
dt.AcceptChanges();
return dt;
}
public static T GetAttributeFrom<T>(object instance, string propertyName) where T : Attribute
{
var attrType = typeof(T);
var property = instance.GetType().GetProperty(propertyName);
return (T)property.GetCustomAttributes(attrType, false).First();
}
}
public class UserInfo
{
[Display(Name = "ID")]
public int UserID { get; set; }
[Display(Name = "FName")]
public string FirstName { get; set; }
[Display(Name = "LName")]
public string LastName { get; set; }
[Display(Name = "Login")]
public string LoginName { get; set; }
[Display(Name = "Company")]
public int CompanyID { get; set; }
}
This is how I build the extension method to convert the class as datatable
public static class EntityExtensions
{
private static readonly string expressionCannotBeNullMessage = "The expression cannot be null.";
private static readonly string invalidExpressionMessage = "Invalid expression.";
public static DataTable ConvertToDataTable<T>(this T instance, Expression<Func<T, object>> proprtiesToSkip = null, Expression<Func<T, object>> proprtiesToRename = null) where T : EntityBase
{
string columnName = "";
string orgPropName;
int counter = 0;
Dictionary<string, string> renameProperties = null;
MemberInfo newName = null;
NewExpression expression = null;
List<string> skipProps = null;
try
{
if (proprtiesToSkip != null )
{
if (proprtiesToSkip.Body is NewExpression)
{
skipProps = new List<string>();
expression = (proprtiesToSkip.Body as NewExpression);
foreach (var cExpression in expression.Arguments)
{
skipProps.Add(GetMemberName(cExpression));
}
}
else
{
throw new ArgumentException("Invalid expression supplied in proprtiesToSkip while converting class to datatable");
}
}
if (proprtiesToRename != null)
{
if (proprtiesToRename.Body is NewExpression)
{
renameProperties = new Dictionary<string, string>();
expression = (proprtiesToRename.Body as NewExpression);
foreach (var cExpression in expression.Arguments)
{
newName = expression.Members[counter];
orgPropName = GetMemberName(cExpression);
renameProperties.Add(orgPropName, newName.Name);
counter++;
}
}
else
{
throw new ArgumentException("Invalid expression supplied in proprtiesToRename while converting class to datatable");
}
}
var properties = instance.GetType().GetProperties().Where(o =>
{
return (skipProps != null && !skipProps.Contains(o.Name, StringComparer.OrdinalIgnoreCase) &&
(o.PropertyType != typeof(System.Data.DataTable) && o.PropertyType != typeof(System.Data.DataSet)));
}).ToArray();
DataTable dataTable = new DataTable();
foreach (PropertyInfo info in properties)
{
columnName = "";
if (renameProperties != null && renameProperties.ContainsKey(info.Name))
{
columnName = renameProperties[info.Name];
}
if (string.IsNullOrEmpty(columnName))
{
columnName = info.Name;
}
dataTable.Columns.Add(new DataColumn(columnName, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
}
object[] values = new object[properties.Length];
for (int i = 0; i < properties.Length; i++)
{
values[i] = properties[i].GetValue(instance);
}
dataTable.Rows.Add(values);
return dataTable;
}
finally
{
renameProperties = null;
newName = null;
expression = null;
skipProps = null;
}
}
private static string GetMemberName(Expression expression)
{
if (expression == null)
{
throw new ArgumentException(expressionCannotBeNullMessage);
}
if (expression is MemberExpression)
{
// Reference type property or field
var memberExpression = (MemberExpression)expression;
return memberExpression.Member.Name;
}
throw new ArgumentException(invalidExpressionMessage);
}
}
Calling it like this
class Program
{
static void Main(string[] args)
{
CancelReason ad = new CancelReason();
ad.BusinessLineID = 1;
ad.CancelReasonCodeID = 2;
ad.CancelRefund = "C";
ad.CompanyID = 0;
ad.DBOperation = 1;
ad.Description = "test";
ad.IsActive = "Y";
ad.ReasonCode = "TestCode";
ad.UpdateStamp = new byte[] { 1, 2, 3, 4 };
DataTable dt = ad.ConvertToDataTable(s => new { s.DBOperation, s.BusinessLineID, s.LoggedInUser }, r => new { Refund = r.CancelRefund, Company = r.CompanyID });
}
}

Create Default Value For Expression<Func<TDto, object>[] In Runtime By Knowing the Type Only

i want to call ProjectTo dynamically
for example on this overload:
public static IQueryable ProjectTo(this IQueryable source, params Expression<Func<TDestination, object>>[] membersToExpand);
like this
Expression.Call(typeof(AutoMapper.QueryableExtensions.Extensions), "ProjectTo", new[] { DtoType },'What goes here as arguments???')
This is the overload that i want to call
public static MethodCallExpression Call(Type type, string methodName, Type[] typeArguments, params Expression[] arguments);
Based on this link:
https://github.com/AutoMapper/AutoMapper/issues/2234#event-1175181678
Update
When i have a parameter like this
IEnumerable<Expression> membersToExpand = new Expression<Func<TDto, object>>[] { };
and pass it to the function like this it works :
Expression.Call(typeof(Extensions), "ProjectTo", new[] { typeof(TDto) }, body, Expression.Constant(membersToExpand));
But when the membersToExpand parameter is null it throw the exception
No generic method 'ProjectTo' on type 'AutoMapper.QueryableExtensions.Extensions' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic
So let me clear The Question:
The 'membersToExpand' have to be not null (OK i know that), when it's not null the 'Expression.Call' can find the suitable overload and it will success But the problem is when the 'membersToExpand' is null so i have to create the default 'membersToExpand' and i have to create it dynamically, i can't create it like this
Expression.Constant(null, typeof(Expression<Func<TDto, object>>[]))
How to create this dynamicaly:
typeof(Expression<Func<TDto, object>>[]
when i just know the 'typeof(TDto)' ??
To reproduce the problem Just copy and Paste and then Run:
class Program
{
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<UserGroupDto, UserGroup>();
cfg.CreateMap<UserGroup, UserGroupDto>()
.ForMember(dest => dest.UserGroupDetails, conf =>
{
conf.MapFrom(ent => ent.UserGroupDetails);
conf.ExplicitExpansion();
});
cfg.CreateMap<UserGroupDetailDto, UserGroupDetail>();
cfg.CreateMap<UserGroupDetail, UserGroupDetailDto>()
.ForMember(dto => dto.UserGroupName, conf => conf.MapFrom(ent => ent.UserGroup.Title));
});
// Someone Wants To Call WITH Members To Expand
Start(new Expression<Func<UserGroupDto, object>>[] { e => e.UserGroupDetails });
Console.WriteLine("===============================================");
// Someone Wants To Call WITHOUT Members To Expand (It's a pain to pass an empty 'new Expression<Func<UserGroupDto, object>>[] { }' so it's better to create the default, dynamically when it's null )
Start(new Expression<Func<UserGroupDto, object>>[] { });
// Someone Wants To Call WITHOUT Members To Expand and pass it null and it will THROW Exception
// Start(null);
Console.ReadLine();
}
static void Start(IEnumerable<Expression> membersToExpand)
{
using (var db = new MyDbContext())
{
IEnumerable projected = CallProjectToDynamically(db.UserGroups.AsQueryable(), typeof(UserGroupDto), membersToExpand);
foreach (var item in projected.OfType<UserGroupDto>())
{
Console.WriteLine(" UserGroupID => " + item.ID);
Console.WriteLine(" UserGroupTitle => " + item.Title);
if (item.UserGroupDetails != null)
item.UserGroupDetails.ToList().ForEach(d =>
{
Console.WriteLine(" UserGroupDetailID => " + d.ID);
Console.WriteLine(" UserGroupDetailTitle => " + d.Title);
Console.WriteLine(" UserGroupDetailUserGroupName => " + d.UserGroupName);
});
}
}
}
static IQueryable CallProjectToDynamically<T>(IQueryable<T> source, Type dtoType, IEnumerable<Expression> membersToExpand)
{
// What i want is this:
// if(membersToExpand == null)
// Create The default dynamically For 'params Expression<Func<TDestination, object>>[] membersToExpand'
var param = Expression.Parameter(typeof(IQueryable<T>), "data");
var body = Expression.Call(typeof(Extensions), "ProjectTo", new[] { dtoType }, param, Expression.Constant(membersToExpand));
return (Expression.Lambda<Func<IQueryable<T>, IQueryable>>(body, param).Compile())(source);
}
}
public partial class MyDbContext : DbContext
{
public MyDbContext()
: base("name=MyDbContext")
{
Database.SetInitializer(new MyDbContextDBInitializer());
}
public virtual DbSet<UserGroup> UserGroups { get; set; }
public virtual DbSet<UserGroupDetail> UserGroupMembers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserGroup>()
.HasMany(e => e.UserGroupDetails)
.WithRequired(e => e.UserGroup)
.HasForeignKey(e => e.UserGroupID);
}
}
public class MyDbContextDBInitializer : DropCreateDatabaseAlways<MyDbContext>
{
protected override void Seed(MyDbContext context)
{
IList<UserGroup> defaultStandards = new List<UserGroup>
{
new UserGroup() { Title = "First UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for First UserGroup" } } },
new UserGroup() { Title = "Second UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for Second UserGroup" } } },
new UserGroup() { Title = "Third UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for Third UserGroup" } } }
};
foreach (UserGroup std in defaultStandards)
context.UserGroups.Add(std);
base.Seed(context);
}
}
public partial class UserGroup
{
public UserGroup()
{
UserGroupDetails = new HashSet<UserGroupDetail>();
}
public long ID { get; set; }
public string Title { get; set; }
public virtual ICollection<UserGroupDetail> UserGroupDetails { get; set; }
}
public partial class UserGroupDetail
{
public long ID { get; set; }
public long UserGroupID { get; set; }
public string Title { get; set; }
public virtual UserGroup UserGroup { get; set; }
}
public partial class UserGroupDto
{
public long ID { get; set; }
public string Title { get; set; }
public IEnumerable<UserGroupDetailDto> UserGroupDetails { get; set; }
}
public partial class UserGroupDetailDto
{
public long ID { get; set; }
public string Title { get; set; }
public long UserGroupID { get; set; }
public string UserGroupName { get; set; }
}
Sincerely.
var call = Expression.Call(typeof(AutoMapper.QueryableExtensions.Extensions), "ProjectTo", new[] { typeof(Bar) },
Expression.Constant(source), Expression.Constant(null, typeof(Expression<Func<Bar, object>>[])));
But I think you're wasting your time. In the latest version, you're not even allowed to pass null.
I solve this problem by wrapping code into generic metod. When I use ProjectTo<T> I don't need to pass second argument. Here is my helper, it takes the type and list of fields and returns DataTable. DB2 is DbContext.
public static class DTODataHelper
{
public static DataTable GetDataOfType(DB2 db, string[] fields, Type type)
{
var method = typeof(DTODataHelper).GetMethod("GetData").MakeGenericMethod(type);
var result = method.Invoke(null, new object[] { db, fields }) as DataTable;
return result;
}
public static DataTable GetData<T>(DB2 db, string[] fields)
{
var dtoType = typeof(T);
var source = Mapper.Configuration.GetAllTypeMaps().Where(i => i.DestinationType == dtoType).Select(i => i.SourceType).FirstOrDefault();
if (source == null)
throw new HMException("Не найден источник данных");
var dbSet = db.Set(source);
var querable = dbSet.AsQueryable();
var list = dbSet.ProjectTo<T>().ToList();
return GetDataTable(list, fields);
}
public static DataTable GetDataTable<T>(IEnumerable<T> varlist, string[] fields)
{
DataTable dtReturn = new DataTable();
PropertyInfo[] oProps = null;
var hasColumnFilter = fields != null && fields.Length > 0;
if (varlist == null) return dtReturn;
foreach (T rec in varlist)
{
if (oProps == null)
{
oProps = rec.GetType().GetProperties();
var excludedProperties = new List<PropertyInfo>();
foreach (PropertyInfo pi in oProps)
{
if (hasColumnFilter && !fields.Contains(pi.Name))
{
excludedProperties.Add(pi);
continue;
}
Type colType = pi.PropertyType;
if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
colType = colType.GetGenericArguments()[0];
}
var piName = pi.GetCustomAttributes().OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? pi.Name;
dtReturn.Columns.Add(new DataColumn(piName, colType));
}
if (excludedProperties.Count > 0)
{
oProps = oProps.Except(excludedProperties).ToArray();
}
}
DataRow dr = dtReturn.NewRow();
foreach (PropertyInfo pi in oProps)
{
var piName = pi.GetCustomAttributes().OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? pi.Name;
dr[piName] = pi.GetValue(rec, null) == null ? DBNull.Value : pi.GetValue(rec, null);
}
dtReturn.Rows.Add(dr);
}
return dtReturn;
}
}

Categories