C# Linq OrderBy Reflection with . deliminated string - c#

I have a need to use a delimited string in order by. EG "Product.Reference".
I seem to be having trouble as the result is ordered the same way it was before the method was called.
For example I have this xUnit test that shows my issue.
The asserts show that the order is still the same.
EDIT:
to be clear, I am not testing Order by, but the method PathToProperty.
This test is for demonstration purposes only.
As you can see from the test I am using reflection in method private static object PathToProperty(object t, string path) So I am assuming I am doing something wrong in there?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;
namespace Pip17PFinanceApi.Tests.Workers
{
public class InputViewModel
{
public List<OrderViewModel> OrderViewModels { get; set; }
}
public class OrderViewModel
{
public Product Product { get; set; }
public decimal Price { get; set; }
}
public class Product
{
public string Description { get; set; }
public string Reference { get; set; }
}
public class OrderByWithReflection
{
[Fact]
public void OrderByTest()
{
//Arrrange
var model = new InputViewModel
{
OrderViewModels = new List<OrderViewModel>
{
new OrderViewModel{
Product = new Product
{
Reference = "02"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "03"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "01"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "04"
}
},
}
};
//Act
var query = model.OrderViewModels.OrderBy(t => PathToProperty(t, "Product.Reference"));
var result = query.ToList();
//Assert
Assert.Equal("01", result[0].Product.Reference);
Assert.Equal("02", result[1].Product.Reference);
Assert.Equal("03", result[2].Product.Reference);
Assert.Equal("04", result[3].Product.Reference);
}
private static object PathToProperty(object t, string path)
{
Type currentType = t.GetType();
foreach (string propertyName in path.Split('.'))
{
PropertyInfo property = currentType.GetProperty(propertyName);
t = property;
currentType = property.PropertyType;
}
return t;
}
}
}

Your PathToProperty isn't correct. Think about it's return value - the last time through, you set t = property and then return t. But property is a PropertyInfo, so you are just comparing identical objects in the OrderBy.
I have a similar extension method I use:
public static object GetPropertyPathValue(this object curObject, string propsPath) {
foreach (var propName in propsPath.Split('.'))
curObject = curObject.GetType().GetProperty(propName).GetValue(curObject);
return curObject;
}
If used in place of your PathToProperty method, the OrderBy will work.
var query = model.OrderViewModels.OrderBy(t => t.GetPropertyPathValue("Product.Reference"));
You could update your method to be something like:
private static object PathToProperty(object curObject, string path) {
foreach (string propertyName in path.Split('.')) {
var property = curObject.GetType().GetProperty(propertyName);
curObject = property.GetValue(curObject);
}
return curObject;
}
for the same result.
PS Actually, using some other extension methods and LINQ, my normal method handles properties or fields:
public static object GetPathValue(this object curObject, string memberPath)
=> memberPath.Split('.').Aggregate(curObject, (curObject, memberName) => curObject.GetType().GetPropertyOrField(memberName).GetValue(curObject));

Related

how to use automapper instance api in extension method

I have a complex domain object structure:
public class CustomerDomainObj
{
public CUSTOMER customer {get;set;}
public ORDER order {get;set;}
public PRODUCT product {get;set;}
}
DTO:
public class CustomerDTO
{
public string cust_name {get;set;}
public string price {get;set;}
public string description {get;set;}
}
I need to map properties of CUSTOMER,PRODUCT and ORDER objects to CustomerDTO
For that I have created an extension method:
public static TDestination Map<TSource, TDestination>(this TDestination destination, TSource source, IMapper mapper)
{
return mapper.Map(source, destination);
}
Configuration:
m.CreateMap<CUSTOMER, CustomerDTO>().ForMember(/*Some Code*/);
m.CreateMap<PRODUCT, CustomerDTO>().ForMember(/*Some Code*/);
m.CreateMap<ORDER, CustomerDTO>().ForMember(/*Some Code*/);
Usage:
var response = _mapper.Map<CustomerDTO>(repoCust.custObj)
.Map(repoCust.prodObj, _mapper);
.Map(repoCust.orderObj, _mapper);
Everything works fine!
Question:
I am looking for a way to skip passing the _mapper instance every time when I try to do some mapping.
Something like :
var response = _mapper.Map<CustomerDTO>(repoCust.custObj)
.Map(repoCust.prodObj);
.Map(repoCust.orderObj);
maybe you are looking for an approach like this...
using AutoMapper;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace sandcastle1
{
class Program
{
static void Main(string[] args)
{
//we create a mapper instance
var _mapper = new MapperConfiguration(m =>
{
m.CreateMap<A, ADto>().ForMember(x => x.Y, opt => opt.MapFrom(x => x.X));
m.CreateMap<B, BDto>().ForMember(x => x.bar, opt => opt.MapFrom(x => x.foo));
}).CreateMapper();
//some dummy POCOs that we can map
var a1 = new A { X = 1 };
var a2 = new A { X = 2 };
var a3 = new A { X = 3 };
var b1 = new B { foo = 1 };
var b2 = new B { foo = 2 };
var b3 = new B { foo = 3 };
//mapping objects with fluent syntax:
// -- we do not repeat the mapper as a written parameter
// -- we do not specify the destination type but assume that there is exactly one TypeMap for the source type
var result = _mapper
.Map(a1)
.Map(a2)
.Map(a3)
.Map(b1)
.Map(b2)
.Map(b3);
//presenting the mapping result
var cnt = 1;
foreach (var dto in result)
{
var propinfo = dto.GetType().GetProperties().First();
Console.WriteLine($"Mapped object {cnt++} is of Type {dto.GetType().Name} with {propinfo.Name} = {propinfo.GetValue(dto)}");
}
}
}
//our dummy classes for the mapping
public class A
{
public int X { get; set; }
}
public class ADto
{
public int Y { get; set; }
}
public class B
{
public int foo { get; set; }
}
public class BDto
{
public int bar { get; set; }
}
public static class ExtensionMethodClass
{
//the first extension method ...
//takes the IMapper and maps the first object
//hands down all possible type mappings or throws an exception when we do not have exactly one mapping for the source type
public static MyFluentResult Map(this IMapper _mapper, object source)
{
//build a dictionary that gives us access to information about existing TypeMaps in the Mapper
//(we only want to know from wicht source type to which destination type)
var maps = _mapper.ConfigurationProvider.GetAllTypeMaps().GroupBy(x => x.SourceType).Where(x => x.Count() == 1).ToDictionary(x => x.Key, x => x.First().DestinationType);
Type stype = source.GetType();
Type dtype;
if (!maps.TryGetValue(stype, out dtype))
{
throw new Exception($"No suitable single mapping found for {stype.Name}");
}
//the magic happens here, we aggregate all necessary information for the following steps in this object
return new MyFluentResult
{
Mapper = _mapper,
Maps = maps,
ResultsSoFar = new object[] { _mapper.Map(source, stype, dtype) }
};
}
//this method is called for the second and all subsequent mapping operations
public static MyFluentResult Map(this MyFluentResult _fluentResult, object source)
{
//same as above, but we already have the dictionary...
Type stype = source.GetType();
Type dtype;
if (!_fluentResult.Maps.TryGetValue(stype, out dtype))
{
throw new Exception($"No suitable single mapping found for {stype.Name}");
}
//again we hand down the result and all other information for the next fluent call
return new MyFluentResult
{
Mapper = _fluentResult.Mapper,
Maps = _fluentResult.Maps,
//we can simply concat the results here
ResultsSoFar = _fluentResult.ResultsSoFar.Concat(new object[] { _fluentResult.Mapper.Map(source, stype, dtype) })
};
}
//omitted implementation for IEnumerable sources... but that would look pretty much the same
}
//the class that holds the aggregated data.... the mapper... the possible typemaps ... and the result data...
//wrapped IEnumerable of the results for convinience
public class MyFluentResult : IEnumerable<object>
{
public IMapper Mapper { get; set; }
public IEnumerable<object> ResultsSoFar { get; set; }
public Dictionary<Type, Type> Maps { get; set; }
public IEnumerator<object> GetEnumerator()
{
return ResultsSoFar.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ResultsSoFar.GetEnumerator();
}
}
}

Trouble getting MemberName of "Name" from CustomAttributes = ColumnAttribute of Linq Table Class

I have a little algo I wrote to compare the Linq DataContext table to the sql table. It rolls through the properties of the Linq table and gets the CustomeAttributes of the property, (table columns). It's been working for years, but somebody created a table field with a # sign in it, (UPS#). Linq doesn't like such a name for its properties for obvious reasons. So, it has a member of the ColumnAttribute called "Name" to handle the swap. But, I've always used the "Storage" member for my column name. You would think you would just pick up the "Name" member if it's present, but I can't find it to save my life.
This is the code. Any help is very much appreciated.
public static ColumnInfo[] GetColumnsInfo(Type linqTableClass)
{
// Just looking in the loop to see if I missed something.
foreach (var fld in linqTableClass.GetProperties())
{
foreach (var attr in fld.CustomAttributes)
{
foreach (var arg in attr.NamedArguments)
{
if (arg.MemberName == "Name")
Debug.WriteLine(arg.MemberName);
Debug.WriteLine("{0}", arg.MemberName);
}
}
}
var columnInfoQuery =
from field in linqTableClass.GetProperties()
from attribute in field.CustomAttributes
from namedArgument in attribute.NamedArguments
where namedArgument.MemberName == "DbType"
select new ColumnInfo
{
//ColumnName = field.Name,
ColumnName = namedArgument.MemberName,
DatabaseType = namedArgument.TypedValue.Value.ToString(),
};
return columnInfoQuery.ToArray();
}
and this is the property in the Table Class:
[global::System.Data.Linq.Mapping.ColumnAttribute(Name="PEER_UPS#", Storage="_PEER_UPS_", DbType="Char(31) NOT NULL", CanBeNull=false)]
public string PEER_UPS_
{
get
{
return this._PEER_UPS_;
}
set
{
if ((this._PEER_UPS_ != value))
{
this.OnPEER_UPS_Changing(value);
this.SendPropertyChanging();
this._PEER_UPS_ = value;
this.SendPropertyChanged("PEER_UPS_");
this.OnPEER_UPS_Changed();
}
}
}
I couldn't find a pretty way to get this done. For some reason the ColumnAttribute just didn't want to play nice. Ugly as this is, it works.
public class ColumnInfo
{
public string ColumnName { get; set; }
public string DatabaseType { get; set; }
}
public static IEnumerable<ColumnInfo> GetColumnsInfo(Type linqTableClass)
{
Debug.WriteLine(string.Format("Table: {0}", linqTableClass.Name));
/// In-Case this has to grow in the future. Using a list for the arg names to search for.
/// The primary arg should be in position 0 of the array.
string dbTypeArgName = "DbType";
string fldPrimayName = "Storage";
string fldSecondaryName = "Name";
List<string> fldArgnames = new List<string>() { fldPrimayName, fldSecondaryName };
foreach (var fld in linqTableClass.GetProperties())
{
Debug.WriteLine(string.Format("Field Name: {0}", fld.Name));
foreach (var attr in fld.GetCustomAttributesData().Cast<CustomAttributeData>()
.Where(r => r.AttributeType == typeof(ColumnAttribute))
.Where(a => a.NamedArguments
.Select(n => n.MemberName)
.Intersect(fldArgnames)
.Any()))
{
var fldName = attr.NamedArguments.Where(r => r.MemberName == fldSecondaryName).Count() != 0
? attr.NamedArguments.Where(r => r.MemberName == fldSecondaryName).SingleOrDefault().TypedValue.Value.ToString()
: fld.Name;
var fldType = attr.NamedArguments
.Where(r => r.MemberName == dbTypeArgName)
.Select(r => r.TypedValue.Value.ToString())
.SingleOrDefault();
Debug.WriteLine(string.Format("\tTable Field Name {0} Table Type {1}", fldName, fldType));
yield return new ColumnInfo()
{
ColumnName = fldName,
DatabaseType = fldType,
};
}
}
}
and here is what i suggest:
[sorry, my first example was indeed too simplistic]
Here is how i'd do it:
namespace LinqAttributes
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
public class ColumnInfo
{
public string ColumnName { get; set; }
public string DatabaseType { get; set; }
}
public class Test
{
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Whatever", Storage = "Whatever", DbType = "Char(20)", CanBeNull = true)]
public string MyProperty { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "PEER_UPS#", Storage = "_PEER_UPS_", DbType = "Char(31) NOT NULL", CanBeNull = false)]
public string PEER_UPS_ { get; set; }
}
internal class Program
{
public static IEnumerable<ColumnInfo> GetColumnsInfo(Type type)
{
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(type))
{
var columnAttribute = descriptor.Attributes
.OfType<System.Data.Linq.Mapping.ColumnAttribute>().SingleOrDefault();
if (columnAttribute != null)
{
yield return new ColumnInfo
{
ColumnName = columnAttribute.Name,
DatabaseType = columnAttribute.DbType
};
}
}
}
private static void Main(string[] args)
{
foreach (var item in GetColumnsInfo(typeof(Test)))
{
Debug.WriteLine(item.ColumnName);
}
}
}
}
Just tested it.
Cheers!
public class City
{
public City() { }
[Column("id_city")]
public int Id { get; private set; }
}
var obj = new City();
var pro = obj.GetType().GetProperties();
string columnAttribute = pro.GetCustomAttributes<ColumnAttribute>().FirstOrDefault().Name;
if(columnAttribute == "id_city") {
//sucess
}

How can I return only selected fields on C# Web API

I have this serviceLayer Method that is used by my Web API proyect to return data to clients:
public IEnumerable<Contactos_view> ListarVistaNew(int activos, string filtro, int idSector, int idClient, string ordenar, int registroInic, int registros)
{
using (var myCon = new AdoNetContext(new AppConfigConnectionFactory(EmpresaId)))
{
using (var rep = base_getRep(myCon))
{
return rep.Listar(activos, filtro, idSector, idClient, ordenar, registroInic, registros);
}
}
}
Now the question is: How can I return only desired property of class Contactos_view? This class contains 20 properties, and my Idea is to add a parameter of type string[] Fields so client can select only the desired propeties.
Is it possible? what would be the returned type of ListarVistaNew in that case?
Thank you!
You can dynamically create and populate expando objects.
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
namespace ClientSelectsProperties
{
public class OriginalType
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
class Program
{
// this simulates your original query result - it has all properties
private static List<OriginalType> queryResult = new List<OriginalType> {
new OriginalType { Id = 1, Name = "one", Description = "one description" },
new OriginalType { Id = 2, Name = "two", Description = "two description" }
};
// "hardcoded" property value readers, go crazy here and construct them dynamically if you want (reflection, code generation...)
private static Dictionary<string, Func<OriginalType, object>> propertyReaders = new Dictionary<string, Func<OriginalType, object>> {
{ "Id", t => t.Id },
{ "Name", t => t.Name },
{ "Description", t => t.Description }
};
static void Main(string[] args)
{
// your client only wants Id and Name
var result = GetWhatClientWants(new List<string> { "Id", "Name" });
}
private static List<dynamic> GetWhatClientWants(List<string> propertyNames)
{
// make sure your queryResult is in-memory collection here. Body of this select cannot be executed in the database
return queryResult.Select(t =>
{
var expando = new ExpandoObject();
var expandoDict = expando as IDictionary<string, object>;
foreach (var propertyName in propertyNames)
{
expandoDict.Add(propertyName, propertyReaders[propertyName](t));
}
return (dynamic)expando;
}).ToList();
}
}
}

Dynamically build an object from a strongly typed class using C#?

Currently, am adding the properties and values to the object manually like this example and sending to Dapper.SimpleCRUD to fetch data from Dapper Orm. This is the desired output I would like to achieve.
object whereCriteria = null;
whereCriteria = new
{
CountryId = 2,
CountryName = "Anywhere on Earth",
CountryCode = "AOE",
IsActive = true
};
The following class should build the object in the above mentioned format and return the ready-made object.
public static class WhereClauseBuilder
{
public static object BuildWhereClause(object model)
{
object whereObject = null;
var properties = GetProperties(model);
foreach (var property in properties)
{
var value = GetValue(property, model);
//Want to whereObject according to the property and value. Need help in this part!!!
}
return whereObject;
}
private static object GetValue(PropertyInfo property, object model)
{
return property.GetValue(model);
}
private static IEnumerable<PropertyInfo> GetProperties(object model)
{
return model.GetType().GetProperties();
}
}
This function WhereClauseBuilder.BuildWhereClause(object model) should return the object in expected format (mentiond above). Here is the implementation of how I would like to use.
public sealed class CountryModel
{
public int CountryId { get; set; }
public string CountryName { get; set; }
public string CountryCode { get; set; }
public bool IsActive { get; set; }
}
public class WhereClauseClass
{
public WhereClauseClass()
{
var model = new CountryModel()
{
CountryCode = "AOE",
CountryId = 2,
CountryName = "Anywhere on Earth",
IsActive = true
};
//Currently, won't return the correct object because the implementation is missing.
var whereClauseObject = WhereClauseBuilder.BuildWhereClause(model);
}
}
Maybe something like that:
private const string CodeTemplate = #"
namespace XXXX
{
public class Surrogate
{
##code##
}
}";
public static Type CreateSurrogate(IEnumerable<PropertyInfo> properties)
{
var compiler = new CSharpCodeProvider();
var compilerParameters = new CompilerParameters { GenerateInMemory = true };
foreach (var item in AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.IsDynamic))
{
compilerParameters.ReferencedAssemblies.Add(item.Location);
}
var propertiesCode =
string.join("\n\n", from pi in properties
select "public " + pi.PropertyType.Name + " " + pi.Name + " { get; set; }");
var source = CodeTemplate.Replace("##code##", propertiesCode);
var compilerResult = compiler.CompileAssemblyFromSource(compilerParameters, source);
if (compilerResult.Errors.HasErrors)
{
throw new InvalidOperationException(string.Format("Surrogate compilation error: {0}", string.Join("\n", compilerResult.Errors.Cast<CompilerError>())));
}
return compilerResult.CompiledAssembly.GetTypes().First(x => x.Name == "Surrogate");
}
And now use it:
public static object BuildWhereClause(object model)
{
var properties = GetProperties(model);
var surrogateType = CreateSurrogate(properties);
var result = Activator.CreateInstance(surrogateType);
foreach (var property in properties)
{
var value = GetValue(property, model);
var targetProperty = surrogateType.GetProperty(property.Name);
targetProperty.SetValue(result, value, null);
}
return result;
}
I didn't compile that. It's only written here. Maybe there are some errors. :-)
EDIT:
To use ExpandoObject you can try this:
public static object BuildWhereClause(object model)
{
var properties = GetProperties(model);
var result = (IDictionary<string, object>)new ExpandoObject();
foreach (var property in properties)
{
var value = GetValue(property, model);
result.Add(property.Name, value);
}
return result;
}
But I don't know whether this will work for you.

How can I use iQueryable in a class with a private constructor?

I've just started learning Linq, and I'm trying to map a database table to a class, but I can't get it to work with a private constructor.
My code:
namespace LinqTest
{
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
[Table(Name = "tblCity")]
public class City
{
private City()
{
}
[Column(IsPrimaryKey = true, Name = "CityId")]
public int Id { get; private set; }
[Column(Name = "CityName")]
public string Name { get; private set; }
public static List<City> GetList()
{
var dataContext = new DataContext(Database.ConnectionString());
var cities = dataContext.GetTable<City>();
var iQueryable =
from city in cities
select city;
return iQueryable.ToList();
}
}
}
I've also tried mapping to a new instance:
var list = new List<City>();
foreach (var iCity in iQueryable)
{
var city = new City
{
Id = iCity.Id,
Name = iCity.Name
};
list.Add(city);
}
return list;
What can I do to make this work? I'm open to alternative methods.
Thanks
You cant use iqueryable object in foreach. Because this is just a query. You must get objects with ToList(). Pls try this:
var list = new List<City>();
foreach (var iCity in iQueryable.ToList())// return iqueryable to list
{
var city = new City
{
Id = iCity.Id,
Name = iCity.Name
};
list.Add(city);
}
return list;
Or You can search automapper. One example here

Categories

Resources