LINQ - Dynamic Select

Tags: C#, LINQ

Problema: Tramite LINQ voglio eseguire una Select indicando tramite stringa la proprietà dell'oggetto da selezionare.

In realtà l'espressione argomento della clausola "Select" deve essere dinamico.

Con una normale query SQL non ci sarebbe nessun problema; basterebbe comporre una semplicissima stringa.

LINQ è leggermente diverso.

Supponiamo di avere una collection di Items dove ogni item è:

public class Item
{
	public int Id { get; set; }
	public string Val1 { get; set; }
	public string Val2 { get; set; }
	public string Val3 { get; set; }
}

Avevo la necessità di rappresentare su un grafico le proprietà "Val1", Val2" ecc... selezionandole da una dropdown.

Ho pensato quindi che mi serviva un modo abbastanza flessibile di "rappresentare ciò che selezionavo".

Ho progettato la parte relativa alla rappresentazione grafica in modo tale da rappresentare una collection di PairValue:

public class PairValue
{
	public PairValue()
	{

	}

	public static PairValue Create(int id, string value)
	{
		return new PairValue(id, value);
	}

	public PairValue(int id, string value)
	{
		Id = id;
		Value = value;
	}
	
	public int Id { get; set; }
	public string Value { get; set; }
}

Nel caso reale l'oggetto Item era composto da circa 30 proprietà; PairValue da proprietà DateTime e float. :P

Mi bastava poter scrivere qualcosa di simile per risolvere il problema.

	List actual = sut.GetPairValues("Val1"); 
	//oppure 
	List actual = sut.GetPairValues("Val2"); 
	//oppure 
	List actual = sut.GetPairValues("Val3"); 
	

Ecco la soluzione:

public class Sut
{
	private IQueryable Items { get; set; } 
	public List GetPairValues(string field) 
	{ 
		List x = Items.Select(MapField(field)).ToList(); 
		return x.ToList(); 
	} 
	
	private Expression<Func> MapField(string field) 
	{
		var param = Expression.Parameter(typeof(T), "Id"); 
		var projection = Expression.Lambda<Func>( 
			Expression.Call(typeof(PairValue), "Create"
			, null
			, Expression.PropertyOrField(param, "Id")
			, Expression.PropertyOrField(param, field)), param); 
			return projection; 
	} 
	
	public Sut() 
	{
		var result = new List 
		{
			new Item { Id=1, Val1="Val11", Val2="Val21", Val3="Val31" }, 
			new Item { Id=2, Val1="Val12", Val2="Val22", Val3="Val32" }, 
			new Item { Id=3, Val1="Val13", Val2="Val23", Val3="Val33" }, 
			new Item { Id=4, Val1="Val14", Val2="Val24", Val3="Val34" }, 
		}; 
		Items = result.AsQueryable(); 
	}
} 

E un paio di test:

[TestClass]
public class Tests
{
	[TestMethod]
	public void SelectVal1()
	{
		Sut sut = new Sut();
		List expected = new List 
		{ 
			new PairValue { Id =1, Value ="Val11" }, 
			new PairValue { Id =2, Value ="Val12" }, 
			new PairValue { Id =3, Value ="Val13" }, 
			new PairValue { Id =4, Value ="Val14" }, 
		}; 
		
		List actual = sut.GetPairValues("Val1"); 
		Assert.IsTrue(expected.Compare(actual)); 
	} 
	
	[TestMethod]
	public void SelectVal2() 
	{ 
		Sut sut = new Sut(); 
		List expected = new List 
		{
			new PairValue { Id =1, Value ="Val21" }, 
			new PairValue { Id =2, Value ="Val22" }, 
			new PairValue { Id =3, Value ="Val23" }, 
			new PairValue { Id =4, Value ="Val24" }, 
		}; 
		
		List actual = sut.GetPairValues("Val2");
		Assert.IsTrue(expected.Compare(actual)); 
	}
	
	[TestMethod] 
	public void SelectVal3() 
	{
		Sut sut = new Sut(); 
		List expected = new List 
		{
			new PairValue { Id = 1, Value = "Val31" }, 
			new PairValue { Id = 2, Value = "Val32" }, 
			new PairValue { Id = 3, Value = "Val33" }, 
			new PairValue { Id = 4, Value = "Val34" }, 
		}; 
		
		List actual = sut.GetPairValues("Val3"); 
		Assert.IsTrue(expected.Compare(actual)); 
	} 
}

public static class ExtensionMethods 
{
	public static bool Compare(this List source, List dest) 
	{ 
		if (source == null && dest == null) 
			return true; 
			
		if (source == null || dest == null)
			return false; 
			
		if (source.Count != dest.Count)
			return false; 
			
		for (int i = 0; i < source.Count; i++) 
		{
			if (source[i].Id != dest[i].Id || source[i].Value != dest[i].Value) 
				return false; 
		} 
		
		return true; 
	}
} 

Add a Comment