Using LINQ to map dynamically (or construct projections) - linq

I know I can map two object types with LINQ using a projection as so:
var destModel = from m in sourceModel
select new DestModelType {A = m.A, C = m.C, E = m.E}
where
class SourceModelType
{
string A {get; set;}
string B {get; set;}
string C {get; set;}
string D {get; set;}
string E {get; set;}
}
class DestModelType
{
string A {get; set;}
string C {get; set;}
string E {get; set;}
}
But what if I want to make something like a generic to do this, where I don't know specifically the two types I am dealing with. So it would walk the "Dest" type and match with the matching "Source" types.. is this possible? Also, to achieve deferred execution, I would want it just to return an IQueryable.
For example:
public IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
{
// dynamically build the LINQ projection based on the properties in TDest
// return the IQueryable containing the constructed projection
}
I know this is challenging, but I hope not impossible, because it will save me a bunch of explicit mapping work between models and viewmodels.

You have to generate an expression tree, but a simple one, so it's not so hard...
void Main()
{
var source = new[]
{
new SourceModelType { A = "hello", B = "world", C = "foo", D = "bar", E = "Baz" },
new SourceModelType { A = "The", B = "answer", C = "is", D = "42", E = "!" }
};
var dest = ProjectionMap<SourceModelType, DestModelType>(source.AsQueryable());
dest.Dump();
}
public static IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
where TDest : new()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var destProperties = typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap = from d in destProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source)));
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, TDest>>(memberInitExpression, itemParam);
projection.Dump();
return sourceModel.Select(projection);
}
(tested in LinqPad, hence the Dumps)
The generated projection expression looks like that :
item => new DestModelType() {A = item.A, C = item.C, E = item.E}

Related

Linq Expression tree

I want to generate expression tree like
StatusRAG.where(x=> (overallRAG.Contains(x.OverallRAG) == overallRAGinclusive) || (costRAG.Contains(x.CostRAG) == costRAGinclusive))
Here is my data:
var overallRAG = new List<string>(){"Red", "Green"}
var costRAG = new List<string>(){"Red", "Amber"}
bool overallRAGinclusive = true
bool costRAGinclusive = false
class StatusRAG
{
public OverallRAG {get; set;}
public CostRAG {get; set;}
}
Linq expression wrong. I think you need not equal of costrag.
StatusRAG.where (x=> overallRAG.Contains(x.OverallRAG) || !costRAG.Contains(x.CostRAG))

Group by an array inside an array using Linq

I can use the below code to group by a ID property in an array which works.
var docArray = MyArray;
var docGroup = docArray.GroupBy(x => x.ID)
.Select(grp => new
{
Id = grp.Key,
Results = grp.ToList(),
}).ToList();
If MyArray has another array inside it which has a property say Data can some please tell me how to do the grouping based on the Data property.
class MyArray
{
SecondArray[] arr = new SecondArray[2];
public int ID{get;set;}
}
class SecondArray
{
public string Data{ get; set; }
}
var query = from a in docArray
from b in a.arr
group new { a, b } by b.Data into g
select new
{
g.Key,
Results = g.ToList()
};

Use Linq.Dynamic or Expressions to create an new anonymous object

I've the following code:
public class C
{
public string Field {get; set;}
public string Data {get; set;}
}
var x = new C { Field = "F", Data = "Data 1" };
var y = new C { Field = "G", Data = "Data 2" };
And I want to cast this to an anonymous object like:
var x_a = new { F = "Data 1" };
var y_a = new { G = "Data 2" };
Note that the property name (F or G) is the content, so this can change dynamically. I'm currently using the System.Linq.Dynamic 'Select' method for this:
public static object CastToAnonymous(this C source)
{
var objects = new List<C>(new [] {source}).AsQueryable().Select("new (Data as " + source.Field + ")") as IEnumerable<object>;
return objects.First();
}
I'm wondering if there is an easier way to achieve this?

How do I create a custom Select lambda expression at runtime to work with sub classes

If I have following type hierarchy:
abstract class TicketBase
{
public DateTime PublishedDate { get; set; }
}
class TicketTypeA:TicketBase
{
public string PropertyA { get; set; }
}
class TicketTypeB:TicketBase
{
public string PropertyB { get; set; }
}
and many more TicketTypes : TicketBase
and want to create a function which selects any property e.g. PropertyA from any ticket type e.g. TicketTypeA
I wrote this function:
private Func<TicketBase, String> CreateSelect(Type t, String FieldName)
{
var parameterExp = Expression.Parameter(t, "sel");
var fieldProp = Expression.PropertyOrField(parameterExp, FieldName);
var lambda = Expression.Lambda<Func<TicketBase, String>>(fieldProp, parameterExp);
return lambda.Compile();
}
and call it on a List<TicketBase> Tickets like so:
Type typeToSelectFrom = typeof(TicketTypeA);
String propertyToSelect = "PropertyA";
Tickets.Select(CreateSelect(typeToSelectFrom, propertyToSelect));
I get the following ArgumentException:
ParameterExpression of type 'TicketTypes.TicketTypeA' cannot be used for delegate parameter of type 'Types.TicketBase'
Anyone know how to fix this?
Well, one option is to include a cast, e.g.
private Func<TicketBase, String> CreateSelect(Type t, String FieldName)
{
var parameterExp = Expression.Parameter(typeof(TicketBase), "sel");
var cast = Expression.Convert(parameterExp, t);
var fieldProp = Expression.PropertyOrField(cast, FieldName);
var lambda = Expression.Lambda<Func<TicketBase, String>>(fieldProp,
parameterExp);
return lambda.Compile();
}
So that calling CreateSelect(typeof(TicketTypeA), "PropertyA") is equivalent to:
Func<TicketBase, string> func = tb => ((TicketTypeA)tb).PropertyA;
Obviously that's going to fail if you apply it to a TicketBase value which refers to (say) a TicketTypeB, but it's hard to avoid that, if you've got a List<TicketBase> or something similar.

linq count/groupby not working

I want to group by the categoryid and then do a count on this. But I don't know how to do this. I have tried a couple of ways without success. Here is my latest:
public class Count
{
public int TradersCount { get; set; }
public int Id { get; set; }
public string Description { get; set; }
}
public IQueryable<Count> CountTradersAttachedToCategories()
{
var data = from tc in _db.tblTradersCategories
select new Count
{
Description = tc.tblCategory.description,
Id = tc.tblCategory.categoryId,
TradersCount = tc.Select(x => x.categoryid).GroupBy().Count()
};
return data;
}
tblTradersCategories joins both
tblTraders/tblCategories
A single trader can have many categories
A single category can have many traders
Thanks in advance for any help.
Clare
Try this:
var data = from tc in _db.tblTradersCategories
group tc by new { tc.tblCategory.categoryId,
tc.tblCategory.description } into g
select new { Count = g.Count(),
Id = g.Key.categoryId,
Description = g.Key.description };
If you want that in your Count class you may need to use AsEnumerable() to perform the conversion in process:
var converted = data.AsEnumerable()
.Select(c => new Count { TradersCount = c.Count,
Id = c.Id,
Description = c.Description });
You can try doing them all in one go:
var data = from tc in _db.tblTradersCategories
group tc by new { tc.tblCategory.categoryId,
tc.tblCategory.description } into g
select new Count { TradersCount = g.Count,()
Id = g.Key.categoryId,
Description = g.Key.description };
But I don't know if that will work. It depends on how the LINQ provider handles it.

Resources