How to get Child Generic? - c#

I define class BaseController with Generic Class TEntity.
public class BaseController<TEntity> : Controller
Use:
public class ProductController : BaseController<Product>
{
public ProductController(BaseService<Product> baseService)
: base(baseService)
{
}
}
Class Product has Child properties relationship. It's dynamic follow TEntity (class Product)
public partial class Product
{
[Key]
public long CodeId { get; set; }
public int Name { get; set; }
//Config Ref
[NotMapped]
public ICollection<ProductDetail> ProductDetails { get; set; }
}
public partial class ProductDetail
{
[Key]
public long CodeId { get; set; }
public int Name { get; set; }
}
TEntity now is Product. And I get property of ProductDetails.
var tEn = JsonUtilities.ConvertObjectToObject<TEntity>(obj);
var propsInfo = (typeof(TEntity)).GetProperties();
var propsCollection = propsInfo.Where(x => x.PropertyType.IsGenericType
&& x.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>));
foreach (var prop in propsCollection)
{
dynamic childItems = prop.GetValue(tEn);
var genericType = prop.PropertyType.GenericTypeArguments[0];
var tableProp = context.GetType().GetProperties().FirstOrDefault(x => x.Name == genericType.Name);
foreach (dynamic itemChild in childItems)
{
// Convert here for setting some property value
var dy = JsonUtilities.ConvertObjectToObject<dynamic>(itemChild);
foreach (KeyValuePair<string, string> itemKey in dic)
{
itemChild[itemKey.Value.Trim()] = "DATA" ; << dynamic property
}
// QUESTION HERE
var itemChild2 = JsonUtilities.ConvertObjectToObject<ABC???>(dy); <<<<<
}
}
Now, how can I can convert again generic itemChild with Class Object ProductDetail
var dy = JsonUtilities.ConvertObjectToObject<ProductDetail>(itemChild); <<< I can not put ProductDetail here because, It's child of generic object Product.
I have TEntity is Product, and in Product have ProductDetails is CHILD. (I've used name Product & ProductDetail for you easy understand.)
Now, I've TEntity is Product. I can get Child Instant of Class ProductDetail. maybe,call 'childClass'.
After that, I convert 'childClass' to dynamic object, My Question is How to convert again to 'childClass' from dynamic Object.

You need to use an Interface. Every type that might be used in such scenario (ex. ProductDetail) should implement that interface. In this case, there is no need to multiple conversions.
I've recapped this.
namespace Project
{
class Product
{
public int Id {get;set;}
public string Name {get;set;}
public ICollection<ProductDetail> ProductDetails {get;set;}
}
class ProductDetail : IInjectable
{
public int Key {get;set;}
public string Value {get;set;}
}
interface IInjectable
{
int Key {get;set;}
string Value {get;set;}
}
class Worker
{
void Do<TEntity>(TEntity entity, Dictionary<int, string> dic)
{
typeof(TEntity)
.GetProperties()
.Where(p => p.PropertyType.IsGenericType &&
typeof(IEnumerable).IsAssignableFrom(p.PropertyType))
.Select(p =>
{
var genType = p.PropertyType
.GetGenericArguments()
.FirstOrDefault();
return new
{
Property = p,
GenericType = genType,
};
})
.Where(item => typeof(IInjectable).IsAssignableFrom(item.GenericType))
.ToList()
.ForEach(item =>
{
var enumerableValue = item.Property
.GetValue(entity) as IEnumerable;
foreach (var v in enumerableValue)
{
if (v is IInjectable injectable)
{
injectable.Value = dic[injectable.Key];
}
}
});
}
}
}

You can make the call of the generic method using reflection.
MethodInfo method = typeof(JsonUtilities).GetMethod(nameof(ConvertObjectToObject));
MethodInfo generic = method.MakeGenericMethod(typeof(prop.PropertyType));
var itemChild2 = generic.Invoke(this, dy);

Related

Using LINQ to select EF properties in DBSet with specific Attributes

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

How to get custom attribute of a property within a property in a class

My task is to the set/initialize all the properties tagged with custom attributes inside a class, derived to the class & properties within properties of that class.
Example:
class Program
{
static void Main(string[] args)
{
A a = new A();
a.InjectableProperties();
}
}
public class A : C
{
[CodeModuleProperty(ModulePropertyType.Injectable)]
public string NameA{ get; set; }
public B ObjB { get; set; }
public IEnumerable<PropertyInfo> InjectableProperties()
{
var response = new List<PropertyInfo>();
// get the mnodule properties
var moduleProperties = GetType().GetProperties();
foreach (var moduleProperty in moduleProperties)
{
var attribute = moduleProperty.GetCustomAttributes(typeof(CodeModulePropertyAttribute), false)
.FirstOrDefault();
if (attribute != null && ((CodeModulePropertyAttribute)attribute).PropertyType ==
ModulePropertyType.Injectable)
{
response.Add(moduleProperty);
}
}
return response // Only gives A,D &C . I also want custom attributes children properties within objB;
}
}
In this method, I also want to get custom attributes properties for property "ClassB" along with properties from Class A, D & E. How to achieve that?
public class B
{
[CodeModuleProperty(ModulePropertyType.Injectable)]
public string NameB { get; set; }
}
public class C : D
{
[CodeModuleProperty(ModulePropertyType.Injectable)]
public string NameC { get; set; }
}
public class D
{
[CodeModuleProperty(ModulePropertyType.Injectable)]
public string NameD { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
public class CodeModulePropertyAttribute : Attribute
{
public CodeModulePropertyAttribute(ModulePropertyType propertyType)
{
PropertyType = propertyType;
}
public ModulePropertyType PropertyType { get; set; }
}
public enum ModulePropertyType
{
Injectable = 1,
DynamicConfiguration = 2
}
For property
object attr = typeof(MyClass)
.GetProperty("NameB")
.GetCustomAttributes(typeof(CodeModuleProperty), false)
.FirstOrDefault();
For class
object attr = typeof(MyClass)
.GetCustomAttributes(typeof(CodeModuleProperty), false)
.FirstOrDefault();
if you want to check all properties, just do .GetProperties and iterate them with for-each and call GetCustomAttributes

pass property to be used in lambda expression

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

Why is my custom member resolver for enum to class conversion not working?

I get the exception message:
The binary operator NotEqual is not defined for the types 'NotificationArea' and 'System.Object'
Source enum:
public enum NotificationArea
{
One,
Two,
Three
}
Destination class:
public class EnumValue
{
public EnumValue()
{
}
public EnumValue(Enum tEnum)
{
Id = Convert.ToInt32(tEnum);
Name = GetEnumValue(tEnum, tEnum.GetType());
}
public int Id { get; set; }
public string Name { get; set; }
public string GetEnumValue(Enum tEnum, Type type)
{
MemberInfo member = type.GetMember(tEnum.ToString())[0];
if (member.GetCustomAttribute<DisplayAttribute>() != null)
{
DisplayAttribute attribute = member.GetCustomAttribute<DisplayAttribute>();
return attribute.Name;
}
return tEnum.ToString();
}
}
Conversion class:
public class EnumConverter : IMemberValueResolver<Notification,
NotificationModel, NotificationArea, EnumValue>
{
public EnumValue Resolve(Notification source, NotificationModel destination,
NotificationArea sourceMember, EnumValue destMember, ResolutionContext context)
{
var model = source.Area.ToDisplay();
return model;
}
}
ToDisplay Extension:
public static EnumValue ToDisplay(this Enum value)
{
var display = new EnumValue(value);
return display;
}
Implementation:
// Query
var notifications = await _db.Notifications
.OrderByDescending(x => x.Timestamp)
.ProjectTo<NotificationModel>(_mapperConfig)
.ToListAsync(token);
// Mapping
CreateMap<Notification, NotificationModel>()
.ForMember(dest => dest.Area, opt => opt.ResolveUsing(src => src.Area));
Source and Destination Models:
public class Notification
{
public int Id {get;set;}
public NotificationArea Area { get; set; }
}
public class NotificationModel
{
public int Id {get;set;}
public EnumValue Area {get;set;}
}
I know I'm doing something wrong... obviously... I just don't know exactly where it's at. I feel Like I have A and C but am missing B.

Calling generic extension method with constraints

I have a model class with two properties of different types - SourceData, DestinationData. These properties holds instances of similar classes (in different namespaces) that I need to compare manually and display differences.
I would like to create generic extension method IsSame for this problem, so I could write simply:
// SourceData: x => ..., DestinatinData: y => ...
var nameIsSame = model.IsSame(x => x.Name, y => y.Name);
var ageIsSame = model.IsSame(x => x.Age, y => y.Age);
...
I prepared sample with this scenario but I can't write the method IsSame that could be called simply like above. I have to specify all type for generic method call.
public interface ISource
{ }
public interface IDestination
{ }
public class SourcePerson : ISource
{
public string Name { get; set; }
public int Age { get; set; }
}
public class DestinationPerson : IDestination
{
public string Name { get; set; }
public int Age { get; set; }
}
public abstract class DetailViewModel<TSource, TDestination>
where TSource : ISource
where TDestination : IDestination
{
public TSource SourceData { get; set; }
public TDestination DestinationData { get; set; }
}
public class PersonDetailViewModel : DetailViewModel<SourcePerson, DestinationPerson>
{ }
Now I have model class of type PersonDetailViewModel that implements abstract class DetailViewModel<>. I thought that following implementation of method ExtensionMethods.IsSame should be called simply by giving instance of PersonDetailViewModel that might be recognized because of constraints.
public static class ExtensionMethods
{
public static bool IsSame<TModel, TSource, TDestination, TValue>(this TModel model, Func<TSource, TValue> sourceProperty, Func<TDestination, TValue> destinationProperty)
where TModel : DetailViewModel<TSource, TDestination>
where TSource : ISource
where TDestination : IDestination
{
if (model.SourceData == null) {
return (model.DestinationData == null);
}
if (model.DestinationData == null)
return false;
return Equals(sourceProperty(model.SourceData), destinationProperty(model.DestinationData));
}
}
However when I try to call the extension method I have to specify all the types even though the compiler should know then all.
class Program
{
static void Main(string[] args)
{
var model = new PersonDetailViewModel
{
SourceData = new SourcePerson { Name = "Karel Gott", Age = 72 },
DestinationData = new DestinationPerson { Name = "Karel Engles", Age = 72 }
};
Console.WriteLine(model);
Console.WriteLine();
//var nameIsSame = model.IsSame(x => x.Name, y => y.Name); // doesn't work :-(
var nameIsSame = model.IsSame<PersonDetailViewModel, SourcePerson, DestinationPerson, string>(x => x.Name, y => y.Name);
Console.WriteLine("Name is same: " + nameIsSame);
//var ageIsSame = model.IsSame(x => x.Age, y => y.Age); // doesn't work :-(
var ageIsSame = model.IsSame<PersonDetailViewModel, SourcePerson, DestinationPerson, int>(x => x.Age, y => y.Age);
Console.WriteLine("Age is same: " + ageIsSame);
Console.WriteLine();
Console.WriteLine("Press any key to exit ...");
Console.ReadKey(true);
}
}
Notice for all: Please don't write me, that I should compare equality differently. I used here method IsSame just for simplicity. The method works well and the code inside should do something else. I just need to call it without explicitly defined types. In my opinion the compiler should know them all from constrains.
In this scenario I used classes PersonDetailViewModel, SourcePerson, DestinationPerson ... but in my application there are plenty of classes like this. The TSource and TDestination doesnt have same code inside, and the properties should have different names. Imagine that I want to compare properties in these classes:
public class SourceCompany : ISource {
public int CompanyId { get; set; }
public string NameTradeRegister { get; set; }
public string AddressStreet { get; set; }
public string Town { get; set; }
}
public class DestinationCompany : IDestination {
public int ID { get; set; }
public string Name { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
}
var idIsSame = model.IsSame(x => CompanyId, y => y.ID);
Ok, I found the solution. The extension method should avoid TModel and instead it can declare DetailViewModel.
public static class ExtensionMethods
{
public static bool IsSame<TSource, TDestination, TValue>(this DetailViewModel<TSource, TDestination> model, Func<TSource, TValue> sourceProperty, Func<TDestination, TValue> destinationProperty)
where TSource : ISource
where TDestination : IDestination
{
if (model.SourceData == null) {
return (model.DestinationData == null);
}
if (model.DestinationData == null)
return false;
return Equals(sourceProperty(model.SourceData), destinationProperty(model.DestinationData));
}
}

Categories