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 });
}
}
Related
I am trying to make a generic method where I can insert any object into a sqlite3 database.
User class:
public class Users : IClassModel<Users>
{
public int Id { get; set; }
public string UserName { get; set; }
public string UserAddress { get; set; }
public string OtherUserDetails { get; set; }
public decimal AmountOfFine { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
}
Interface class:
public interface IClassModel<T>
{
public int Id { get; set; }
}
QueryBuilder class:
public class queryBuilder : IDisposable
{
private SqliteConnection _connection;
public queryBuilder(string connectionString)
{
_connection = new SqliteConnection(connectionString);
_connection.Open();
}
public void Dispose()
{
_connection.Close();
}
public void Create<T>(T obj) where T : IClassModel<T>
{
// insert into tableName values()
Type myType = obj.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
ArrayList valueArray = new ArrayList();
ArrayList nameArray = new ArrayList();
var questionString = "";
var nameString = "";
foreach (PropertyInfo prop in props)
{
object propValue = prop.GetValue(obj, null);
object propName = prop.Name;
valueArray.Add(propValue);
nameArray.Add(propName);
questionString += "?, ";
nameString += $"{propName}, " ;
}
var newNameString = nameString.Trim();
var newerNameString = newNameString.TrimEnd(',');
var newQuestionString = questionString.Trim();
var newerQuestionString = newQuestionString.TrimEnd(',');
SqliteCommand insertSQL = new SqliteCommand($"INSERT INTO {typeof(T).Name} ({newerNameString}) VALUES ({newerQuestionString})", _connection);
foreach (var item in valueArray)
{
insertSQL.Parameters.Add(item);
}
insertSQL.ExecuteNonQuery();
//Console.WriteLine("Successfully added the thing.");
}
}
Driver:
using Microsoft.Data.Sqlite;
using QueryBuilder.Models;
using System.Reflection;
using (var query = new queryBuilder(#"Data Source=C:\path\to\database"))
{
// con
var user = new Users();
user.UserName = "username";
user.UserAddress = "some_address";
user.OtherUserDetails = "details";
user.AmountOfFine = 90;
user.Email = "something#email.com";
user.PhoneNumber = "5555555555";
query.Create<Users>(user);
}
I know my code is bit messy, but the idea is to somehow create an object and then be able to insert it into the already made table, no matter what object it is. I keep getting invalid cast exceptions.
I need to be able to iterate through the values and properties and add them to the sqlite insert command but it doesn't seem to be working. Any help is appreciated.
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};
}
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;
}
}
As the title states, I would like to build a dynamic expression using propertyInfo and a value.
Currently i have something like this
public static IQueryable<T> WhereValueEquals<T>(this IQueryable<T> q, FieldFor fieldFor, string fieldValue)
where T : ModuleData
{
switch (fieldFor)
{
case FieldFor.Name:
return q.Where(e => e.Name == fieldValue);
case FieldFor.Reference:
return q.Where(e => e.Reference == fieldValue);
case FieldFor.Text0:
return q.Where(e => e.Reference == fieldValue);
default:
return q;
}
}
Now the case statement is only going to get longer and longer, but this was fine during early stages of development.
Now, using the FieldFor enumeration, i can get the propertyInfo using an extension i already wrote. So how can i return an IQueryable from WhereValueEquals using the string value passed in and a propertyInfo
So far i have
public static IQueryable<T> WhereValueEquals<T>(this IQueryable<T> q, FieldFor fieldFor, string fieldValue)
where T : ModuleData
{
PropertyInfo propertyInfo = typeof(T).GetFieldProperties().GetPropertyByFieldFor(fieldFor);
//return q.Where(expression);
}
At some point in the time I had to do something similar, and there are a ton of code on Stackoverflow that show you how to build an expression builder. Well this is my POC, hope it helps you
void Main()
{
var ops = new List<Ops>
{
new Ops
{
//OperandType = typeof(string),
OpType=OpType.Equals,
OperandName = "Name",
ValueToCompare = "MM"
},
new Ops
{
//OperandType = typeof(int),
OpType=OpType.Equals,
OperandName = "ID",
ValueToCompare = 1
},
};
var testClasses = new List<TestClass>
{
new TestClass { ID =1, Name = "MM", Date = new DateTime(2014,12,1)},
new TestClass { ID =2, Name = "BB", Date = new DateTime(2014,12,2)}
};
var funct = ExpressionBuilder.BuildExpressions<TestClass>(ops);
foreach(var item in testClasses.Where(funct))
{
Console.WriteLine("ID " +item.ID);
Console.WriteLine("Name " +item.Name);
Console.WriteLine("Date" + item.Date);
}
}
// Define other methods and classes here
public enum OpType
{
Equals
}
public class Ops
{
//public Type OperandType {get; set;}
public OpType OpType {get; set;}
public string OperandName {get;set;}
public object ValueToCompare {get;set;}
}
public class TestClass
{
public int ID {get;set;}
public string Name {get; set;}
public DateTime Date {get;set;}
}
public class ExpressionBuilder
{
public static Func<T,bool> BuildExpressions<T>( List<Ops> opList)
{
Expression currentExpression= null;
var parameter = Expression.Parameter(typeof(T), "prop");
for(int i =0; i< opList.Count; i++)
{
var op = opList[i];
Expression innerExpression = null;
switch(op.OpType)
{
case OpType.Equals :
{
var innerParameter = Expression.Property(parameter,op.OperandName);
var ConstExpression = Expression.Constant(op.ValueToCompare);
innerExpression = Expression.Equal(innerParameter, ConstExpression);
break;
}
}
if (i >0)
{
currentExpression = Expression.And(currentExpression, innerExpression);
}
else
{
currentExpression = innerExpression;
}
}
var lambdaExpression = Expression.Lambda<Func<T,bool>>(currentExpression, new []{parameter});
Console.WriteLine(lambdaExpression);
return lambdaExpression.Compile() ;
}
}
I have a class, which is created and populated from an xml string, I've simplified it for example purposes:
[XmlRoot("Person")]
public sealed class Person
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Location")]
public string Location { get; set; }
[XmlElement("Emails", Type = typeof(PersonEmails)]
public PersonEmails Emails { get; set; }
}
public class PersonEmails
{
[XmlElement("Email", Type = typeof(PersonEmail))]
public PersonEmail[] Emails { get; set; }
}
public class PersonEmail
{
[XmlAttribute("Type")]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
To extract the information, I'm trying to load them into another class, which is simply:
public class TransferObject
{
public string Name { get; set; }
public ObjectField[] Fields { get; set; }
}
public class ObjectField
{
public string Name { get; set; }
public string Value { get; set; }
}
I'm only populating "Fields" from the other object, which would simply be (Name = "Location", Value = "London"), but for Emails, (Name = "Email"+Type, Value = jeff#here.com)
Currently I can populate all the other fields, but I'm stuck with Emails, and knowing how to dig deep enough to be able to use reflection (or not) to get the information I need. Currently I'm using:
Person person = Person.FromXmlString(xmlString);
List<ObjectField> fields = new List<ObjectField>();
foreach (PropertyInfo pinfo in person.getType().GetProperties()
{
fields.Add(new ObjectField { Name = pinfo.Name, Value = pinfo.getValue(person, null).ToString();
}
How can I expand on the above to add all my emails to the list?
You are trying to type cast a complex values type to string value so you lost the data. Instead use following code:
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "Person One";
person.Location = "India";
person.Emails = new PersonEmails();
person.Phones = new PersonPhones();
person.Emails.Emails = new PersonEmail[] { new PersonEmail() { Type = "Official", Value = "xyz#official.com" }, new PersonEmail() { Type = "Personal", Value = "xyz#personal.com" } };
person.Phones.Phones = new PersonPhone[] { new PersonPhone() { Type = "Official", Value = "789-456-1230" }, new PersonPhone() { Type = "Personal", Value = "123-456-7890" } };
List<ObjectField> fields = new List<ObjectField>();
fields = GetPropertyValues(person);
}
static List<ObjectField> GetPropertyValues(object obj)
{
List<ObjectField> propList = new List<ObjectField>();
foreach (PropertyInfo pinfo in obj.GetType().GetProperties())
{
var value = pinfo.GetValue(obj, null);
if (pinfo.PropertyType.IsArray)
{
var arr = value as object[];
for (var i = 0; i < arr.Length; i++)
{
if (arr[i].GetType().IsPrimitive)
{
propList.Add(new ObjectField() { Name = pinfo.Name + i.ToString(), Value = arr[i].ToString() });
}
else
{
var lst = GetPropertyValues(arr[i]);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
else
{
if (pinfo.PropertyType.IsPrimitive || value.GetType() == typeof(string))
{
propList.Add(new ObjectField() { Name = pinfo.Name, Value = value.ToString() });
}
else
{
var lst = GetPropertyValues(value);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
return propList;
}
}
Check this snippet out:
if(pinfo.PropertyType.IsArray)
{
// Grab the actual instance of the array.
// We'll have to use it in a few spots.
var array = pinfo.GetValue(personObject);
// Get the length of the array and build an indexArray.
int length = (int)pinfo.PropertyType.GetProperty("Length").GetValue(array);
// Get the "GetValue" method so we can extact the array values
var getValue = findGetValue(pinfo.PropertyType);
// Cycle through each index and use our "getValue" to fetch the value from the array.
for(int i=0; i<length; i++)
fields.Add(new ObjectField { Name = pinfo.Name, Value = getValue.Invoke(array, new object[]{i}).ToString();
}
// Looks for the "GetValue(int index)" MethodInfo.
private static System.Reflection.MethodInfo findGetValue(Type t)
{
return (from mi in t.GetMethods()
where mi.Name == "GetValue"
let parms = mi.GetParameters()
where parms.Length == 1
from p in parms
where p.ParameterType == typeof(int)
select mi).First();
}
You can definately do it with Reflection... You can take advantage of the fact that a Type can tell you if it's an array or not (IsArray)... and then take advantage of the fact that an Array has a method GetValue(int index) that will give you a value back.
Per your comment
Because Emails is a property within a different class, recursion should be used. However the trick is knowing when to go to the next level. Really that is up to you, but
if it were me, I would use some sort of Attribute:
static void fetchProperties(Object instance, List<ObjectField> fields)
{
foreach(var pinfo in instance.GetType().GetProperties())
{
if(pinfo.PropertyType.IsArray)
{
... // Code described above
}
else if(pinfo.PropertyType.GetCustomAttributes(typeof(SomeAttribute), false).Any())
// Go the next level
fetchProperties(pinfo.GetValue(instance), fields);
else
{
... // Do normal code
}
}
}