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

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

Related

Is it possible to add GroupBy to a IQueryable<T> query?

I am trying to genericize some repeated stuff but struggling with this. I don't even know if it's even possible. I put what I need inside the code block but I'd be happy to clarify if need be. Any help is appreciated.
public static async Task<object> LoadData<T>(IQueryable<T> query, string RequestGroup) where T : class
{
//this works
//var Groupped = query.AsNoTracking().ToList().GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() });
//How can I achieve this?
var Groupped = query.GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).AsNoTracking().ToListAsync();
return new { Data = Groupped };
}
public async Task<JsonResult> GetData()
{
var query = _context.Samples.AsQueryable();
return Json(await LoadData(query, "Description"));
}
public class Sample
{
public int SampleID { get; set; }
public string Description { get; set; }
}
#region Extensions
public static object GetPropertyValue(this object obj, string name)
{
foreach (string part in name.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
public static T GetPropertyValue<T>(this object obj, string name)
{
object retval = GetPropertyValue(obj, name);
if (retval == null) { return default(T); }
// throws InvalidCastException if types are incompatible
return (T)retval;
}
#endregion
Thanks coder_b, I used another response from the link you provided and got what I am looking for. Here is the answer if anyone else needs it:
public static async Task<object> LoadData<T>(IQueryable<T> query, string RequestGroup) where T : class
{
//this works
//var Groupped = query.AsNoTracking().ToList().GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() });
//How can I achieve this?
//var Groupped = query.GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).AsNoTracking().ToListAsync();
//Answer
var Groupped = query.GroupBy(GetGroupKey<T>(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).ToList();
return new { Data = Groupped };
}
public async Task<JsonResult> GetData()
{
var query = _context.Samples.AsQueryable();
return Json(await LoadData(query, "Description"));
}
public class Sample
{
public int SampleID { get; set; }
public string Description { get; set; }
}
#region Extensions
private static Expression<Func<T, string>> GetGroupKey<T>(string property)
{
var parameter = Expression.Parameter(typeof(T));
var body = Expression.Property(parameter, property);
return Expression.Lambda<Func<T, string>>(body, parameter);
}
#endregion

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)

How to create dynamic NRules

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

Generic way to link to classes/objects

I'd like to write a generic extension method to link two classes/objects
Classes:
public class Model1
{
public Guid Model2ID { get; set; }
public Model2 Model2 { get; set; }
// .. other insignificant properties to this question
}
public class Model2
{
public Guid ID { get; set; }
}
I'm trying to write a method that looks something like:
public static class ObjectExtensions
{
public static Void LinkTo<T,U>(
this T m1,
IEnumerable<U> m2,
m1p // expression to select what property on m1 is populated
Action<bool,T,IEnumerable<U>> expression)
{
// not sure about this part at all
m1p = u2.FirstOrDefault(expression);
}
}
Usage:
var listOfModel2 = //....
Model1.LinkTo(listOfModel2, m => m.Model2, (m1,m2) m1.Model2Id == m2.ID);
As we had discussed in Chat, I'd recommend a light-weight version of EF Context (with reflection). This is completely custom and dynamic, you simply need to use KeyAttribute and ForeignKeyAttribute on your models and add the models to your this custom Context. I only wired up the Add, as the rest you should be able to figure out on your own.
Classes:
public class Staff
{
[Key]
public Guid Id { get; set; }
[ForeignKey("Contact")]
public Guid ContactId { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("Dog")]
public Guid DogId { get; set; }
public Dog Dog { get; set; }
}
public class Dog
{
[Key]
public Guid Id { get; set; }
}
Context:
public class Context
{
//Add as many of these as you want. Don't forget to make public properties for them!
private ObservableCollection<Staff> _staffs = new ObservableCollection<Staff>();
private ObservableCollection<Contact> _contacts = new ObservableCollection<Contact>();
private ObservableCollection<Dog> _dogs = new ObservableCollection<Dog>();
private List<IForeignKeyRelation> _relations = new List<IForeignKeyRelation>();
public Context()
{
var observables = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.ToList();
foreach(var observable in observables)
{
var notifyCollection = observable.GetValue(this) as INotifyCollectionChanged;
if (notifyCollection != null)
{
notifyCollection.CollectionChanged += CollectionChanged;
Type principalType = observable.FieldType.GetGenericArguments()[0];
var relations = principalType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.Where(p => p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute != null)
.Select(p => new { PrincipalForeignKeyInfo = p, ForeignKey = p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute })
.Where(p => principalType.GetProperty(p.ForeignKey.Name) != null)
.Select(p => {
var principalForeignKeyInfo = p.PrincipalForeignKeyInfo;
var principalRelationInfo = principalType.GetProperty(p.ForeignKey.Name);
var dependantType = principalRelationInfo.PropertyType;
var dependantKeyProperties = dependantType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.Where(dp => dp.GetCustomAttribute(typeof(KeyAttribute)) as KeyAttribute != null)
.ToList();
var dependantKeyInfo = dependantKeyProperties.FirstOrDefault();
var isValid = (dependantKeyInfo != null)
// Don't allow INT to GUID comparisons
// Keys need to be of same type;
&& (principalForeignKeyInfo.PropertyType == dependantKeyInfo.PropertyType);
return new {
IsValid = isValid,
PrincipalRelationInfo = principalRelationInfo,
DependantType = dependantType,
PrincipalCollection = observable.GetValue(this),
PrincipalForeignKeyInfo = principalForeignKeyInfo,
DependantKeyInfo = dependantKeyInfo
};
})
.Where(r => r.IsValid)
.Select(r =>
{
var relationType = typeof(ForeignKeyRelation<,>).MakeGenericType(principalType, r.DependantType);
var relation = Activator.CreateInstance(relationType) as IForeignKeyRelation;
relation.GetType().GetProperty("PrincipalCollection").SetValue(relation, observable.GetValue(this));
relation.DependantKeyInfo = r.DependantKeyInfo;
relation.PrincipalForeignKeyInfo = r.PrincipalForeignKeyInfo;
relation.PrincipalRelationInfo = r.PrincipalRelationInfo;
return relation;
})
.ToList();
_relations.AddRange(relations);
}
}
}
// Makes storing Generic types easy when the
// Generic type doesn't exist;
private interface IForeignKeyRelation
{
PropertyInfo PrincipalForeignKeyInfo { get; set; }
PropertyInfo PrincipalRelationInfo { get; set; }
PropertyInfo DependantKeyInfo { get; set; }
void Add<T>(T value);
}
// Class to hold reflected values
// Reflection
private class ForeignKeyRelation<P,D> : IForeignKeyRelation
{
// Staff.ContactId
public PropertyInfo PrincipalForeignKeyInfo { get; set; }
public Collection<P> PrincipalCollection { get; set; }
// Staff.Contact
public PropertyInfo PrincipalRelationInfo { get; set; }
// Contact.Id
public PropertyInfo DependantKeyInfo { get; set; }
public void Add<T>(T value)
{
if (value.GetType() == typeof(D))
{
var dependantKey = DependantKeyInfo.GetValue(value);
var principals = PrincipalCollection.Where(p => this.PrincipalForeignKeyInfo.GetValue(p).Equals(dependantKey))
.ToList();
foreach(var principal in principals)
{
PrincipalRelationInfo.SetValue(principal, value);
}
}
}
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
foreach(var relation in this._relations)
{
foreach(var item in args.NewItems)
{
relation.Add(item);
}
}
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
break;
case NotifyCollectionChangedAction.Replace:
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
throw new NotImplementedException(args.Action.ToString());
}
}
public IList<Staff> Staffs
{
get
{
return _staffs;
}
}
public IList<Contact> Contacts
{
get
{
return _contacts;
}
}
public IList<Dog> Dogs
{
get
{
return _dogs;
}
}
}
Simple example program:
public static void Main()
{
var context = new Context();
var staff = new Staff() { Id = Guid.NewGuid() };
var contact = new Contact();
contact.Id = Guid.NewGuid();
contact.Name = "Hello DotFiddle!";
staff.ContactId = contact.Id;
context.Staffs.Add(staff);
Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString());
context.Contacts.Add(contact);
Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString());
Console.WriteLine("Staff.Contact.Name: "+ staff.Contact.Name);
}
result:
staff contact is null: True
staff contact is null: False
Staff.Contact.Name: Hello DotFiddle!
This Entire Example on DotNetFiddle.net
I don't know exactly what you're going for, but one of these accomplishes it: The bottom two are 'iffy' as you can see by all the if statements. Simply put there's no way for the compiler to be able to be sure that they'll work, since you can easily pass a bad propertyExpression.
class Program
{
static void Main(string[] args)
{
var guids = Enumerable.Range(0, 10).Select(i => Guid.NewGuid()).ToList();
var m2s = guids.Select(g => new Model2 { ID = g }).ToList();
var model1 = new Model1 { Model2ID = m2s[4].ID };
model1.LinkTo(m2s, (m1, m2) => m1.Model2 = m2, (m1, m2) => m2.ID == m1.Model2ID);
var model1a = new Model1 { Model2ID = m2s[4].ID };
model1a.LinkTo(m2s, m1 => m1.Model2, m1 => m1.Model2ID, m2 => m2.ID);
var model1b = new Model1 { Model2ID = m2s[4].ID };
model1b.LinkTo(m2s, m1 => m1.Model2, (m1, m2) => m1.Model2ID == m2.ID);
}
}
public static class ObjectExtensions
{
public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Action<T, U> action, Func<T, U, bool> filter)
{
if (m2s.Any(m2 => filter(m1, m2)))
{
var x = m2s.First(m2 => filter(m1, m2));
action(m1, x);
}
}
public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, U, bool> filter)
{
var results = m2s.Where(m2 => filter(m1, m2));
if (!results.Any())
return;
var x = results.FirstOrDefault();
if (x != null)
{
var me = (propertyExpression.Body as MemberExpression);
if (me != null)
{
var pi = me.Member as PropertyInfo;
if (pi != null)
{
var setter = pi.GetSetMethod();
if (setter != null)
{
setter.Invoke(m1, new object[] { x });
}
}
}
}
}
public static void LinkTo<T, U, Key>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, Key> tKey, Func<U, Key> uKey)
{
var results = Enumerable.Repeat(m1, 1)
.Join(m2s, tKey, uKey, (t, u) => u);
if(!results.Any())
return;
var x = results
.FirstOrDefault();
if (x != null)
{
var me = (propertyExpression.Body as MemberExpression);
if (me != null)
{
var pi = me.Member as PropertyInfo;
if (pi != null)
{
var setter = pi.GetSetMethod();
if (setter != null)
{
setter.Invoke(m1, new object[] { x });
}
}
}
}
}
}
You should read about extension methods. They are called "from" object.
For example you can write generic extension method as below
class Program
{
static void Main(string[] args)
{
var listOfModel2 = new Model1();
//You can call it from "object"
listOfModel2.MyExtensionMethod();
//Or directly as it is declared
ObjectExtensions.MyExtensionMethod(listOfModel2);
}
}
public static class ObjectExtensions
{
public static void MyExtensionMethod<T>(this T t)
{
//Do somthing
}
}

How to deep copy members of a strongly typed collection

I have two classes XmlPerson and Person, each class has public properties, no methods nor any fields.
How would I deep copy all the properties from Person to XmlPerson? I dont want to use a third-party library like MiscUtil.PropertyCopy or Automapper. I have managed to copy the "first-level" properties that are primitive types and strongly typed objects but when it comes the List I have no idea.
The structure of the Person class is below:
public class Person
{
public string FirstName { get; set; }
public string Surname { get; set; }
public decimal? Salary { get; set; }
public List<AddressDetails> AddressDetails { get; set; }
public NextOfKin NextOfKin { get; set; }
}
public class NextOfKin
{
public string FirstName { get; set; }
public string Surname { get; set; }
public string ContactNumber { get; set; }
public List<AddressDetails> AddressDetails { get; set; }
}
public class AddressDetails
{
public int HouseNumber { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
}
thank u for the help.
charles
Here is the what I have so far:
public class XmlTestCaseToClassMapper
{
internal TTarget MapXmlClassTotargetClass(TSource xmlPerson)
{
var targetObject = Activator.CreateInstance();
var sourceObject = Activator.CreateInstance();
//var xmlClassProperties = xmlPerson.GetType().GetProperties().ToList().OrderBy(x => x.Name);
var xmlClassProperties = GetProperties(xmlPerson.GetType());
//var targetClassProperties = targetObject.GetType().GetProperties().ToList().OrderBy(x => x.Name);
var targetClassProperties = GetProperties(targetObject.GetType());
PropertyInfo targetClassProperty = null;
foreach (var xmlProperty in xmlClassProperties)
{
if (!xmlProperty.PropertyType.IsClass || xmlProperty.PropertyType.UnderlyingSystemType == typeof(string)
|| xmlProperty.PropertyType.IsPrimitive)
{
targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == xmlProperty.Name);
var propertyValue = xmlProperty.GetValue(xmlPerson, null);
targetClassProperty.SetValue(targetObject, propertyValue, null);
}
else if (xmlProperty.PropertyType.UnderlyingSystemType == typeof(NextOfKin)) //Check subType of the property
{
var subPropertyInstance = Activator.CreateInstance(xmlProperty.GetType());
var subProperties = GetProperties(xmlProperty.GetType());
subProperties.ForEach(subProperty =>
{
targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == subProperty.Name && x.GetType().IsClass);
targetClassProperty.SetValue(subPropertyInstance, xmlProperty.GetValue(this, null), null);
});
}
//else if (xmlProperty.PropertyType.IsGenericType)
//{
// var xmlGenericType = xmlProperty.PropertyType.GetGenericArguments().First();
// var xmlGenericTypeProperties = GetProperties(xmlGenericType);
// targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == xmlProperty.Name);
// var targetGenericType = targetClassProperty.PropertyType.GetGenericArguments().First();
// var targetGenericProperties = GetProperties(targetGenericType);
// Type targetGenericList = typeof(List<>).MakeGenericType(new Type[] { targetGenericType });
// object listInstance = Activator.CreateInstance(targetGenericList);
// //foreach (var xmlGenericProperty in xmlGenericTypeProperties)
// //{
// // var targetGenericProperty = targetGenericProperties.FirstOrDefault(x => x.Name == xmlGenericProperty.Name);
// // targetGenericProperty.SetValue(targetGenericProperty, xmlGenericProperty.GetValue(xmlGenericType, null), null);
// //}
// xmlGenericTypeProperties.ForEach(x =>
// {
// foreach (var targetGenericProperty in targetGenericProperties)
// {
// targetGenericProperty.SetValue(targetGenericProperty, targetGenericProperty.GetValue(x, null), null);
// }
// });
//}
//}
}
return targetObject;
}
private List<PropertyInfo> GetProperties(Type targetType)
{
var properties = new List<PropertyInfo>();
targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList().ForEach(property =>
{
properties.Add(property);
});
return properties.OrderBy(x => x.Name).ToList();
}
}
Here is a possible solution. It probably requires some tweaks based on your actual classes.
public T DeepCopy<S, T>(S source) where T : new()
{
var sourceProperties = typeof(S).GetProperties(BindingFlags.Instance | BindingFlags.Public);
T target = new T();
foreach (var sourceProperty in sourceProperties)
{
var property = typeof(T).GetProperty(sourceProperty.Name);
if (property.PropertyType.IsPrimitive ||
property.PropertyType == typeof(string) ||
(property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
object value = sourceProperty.GetValue(source);
property.SetValue(target, value);
}
else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
{
var sourceList = (IEnumerable)sourceProperty.GetValue(source);
if (sourceList != null)
{
var deepCopy = this.GetType().GetMethod("DeepCopy").MakeGenericMethod(sourceProperty.PropertyType.GenericTypeArguments[0], property.PropertyType.GenericTypeArguments[0]);
var ctor = property.PropertyType.GetConstructor(Type.EmptyTypes);
IList targetList = (IList) ctor.Invoke(null);
foreach (var element in sourceList)
{
targetList.Add(deepCopy.Invoke(this, new object[] { element } ));
}
property.SetValue(target, targetList);
}
}
else
{
var value = sourceProperty.GetValue(source);
if (value != null)
{
var deepCopy = this.GetType().GetMethod("DeepCopy").MakeGenericMethod(sourceProperty.PropertyType, property.PropertyType);
property.SetValue(target, deepCopy.Invoke(this, new object[] { value }));
}
}
}
return target;
}

Categories