I try do implement a user dynamic filter, where used selects some properties, selects some operators and selects also the values.
As I didn't find yet an answer to this question, I tried to use LINQ expressions.
Mainly I need to identify all houses which main rooms are kitchens(any sens, I know).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//using System.Linq.Dynamic;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Room aRoom = new Room() { Name = "a Room" };
Room bRoom = new Room() { Name = "b Room" };
Room cRoom = new Room() { Name = "c Room" };
House myHouse = new House
{
Rooms = new List<Room>(new Room[] { aRoom }),
MainRoom = aRoom
};
House yourHouse = new House()
{
Rooms = new List<Room>(new Room[] { bRoom, cRoom }),
MainRoom = bRoom
};
House donaldsHouse = new House()
{
Rooms = new List<Room>(new Room[] { aRoom, bRoom, cRoom }),
MainRoom = aRoom
};
var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse });
//var kitchens = houses.AsQueryable<House>().Where("MainRoom.Type = RoomType.Kitchen");
//Console.WriteLine("kitchens count = {0}", kitchens.Count());
var houseParam = Expression.Parameter(typeof(House), "house");
var houseMainRoomParam = Expression.Property(houseParam, "MainRoom");
var houseMainRoomTypeParam = Expression.Property(houseMainRoomParam, "Type");
var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType");
var comparison = Expression.Lambda(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant("Kitchen", typeof(RoomType)))
);
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
Console.WriteLine("kitchens count = {0}", kitchens.Count());
Console.ReadKey();
}
}
public class House
{
public string Address { get; set; }
public double Area { get; set; }
public Room MainRoom { get; set; }
public List<Room> Rooms { get; set; }
}
public class Room
{
public double Area { get; set; }
public string Name { get; set; }
public RoomType Type { get; set; }
}
public enum RoomType
{
Kitchen,
Bedroom,
Library,
Office
}
}
var kitchens = from h in houses
where h.MainRoom.Type == RoomType.Kitchen
select h;
But you must set the RoomType property on the rooms before.
Ok, edit:
so you must redefine:
var comparison = Expression.Lambda<Func<House, bool>>(...
Then, when you use it:
var kitchens = houses.AsQueryable().Where(comparison.Compile());
Edit #2:
Ok, here you go:
var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType");
// ???????????????????????? DOES NOT WORK
var comparison = Expression.Lambda<Func<House, bool>>(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant(Enum.Parse(typeof(RoomType), "Kitchen"), typeof(RoomType))), houseParam);
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
Edit #3: Of, for your needs, I am out of ideas for now. I give you one last one:
Declare an extension method on the String type:
internal static object Prepare(this string value, Type type)
{
if (type.IsEnum)
return Enum.Parse(type, value);
return value;
}
Then use it in that expression like:
Expression.Constant("Kitchen".Prepare(typeof(RoomType)), typeof(RoomType))
That's because apparently enums are treated differently. That extension will leave the string unaltered for other types. Drawback: you have to add another typeof() there.
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
The Where method takes a Func<House, bool> or a Expression<Func<House, bool>> as the parameter, but the variable comparison is of type LambdaExpression, which doesn't match. You need to use another overload of the method:
var comparison = Expression.Lambda<Func<House, bool>>(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant("Kitchen", typeof(RoomType))));
//now the type of comparison is Expression<Func<House, bool>>
//the overload in Expression.cs
public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters);
I wouldn't build the where clause in that way - I think it's more complex than it needs to be for your needs. Instead, you can combine where clauses like this:
var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse });
// A basic predicate which always returns true:
Func<House, bool> housePredicate = h => 1 == 1;
// A room name which you got from user input:
string userEnteredName = "a Room";
// Add the room name predicate if appropriate:
if (!string.IsNullOrWhiteSpace(userEnteredName))
{
housePredicate += h => h.MainRoom.Name == userEnteredName;
}
// A room type which you got from user input:
RoomType? userSelectedRoomType = RoomType.Kitchen;
// Add the room type predicate if appropriate:
if (userSelectedRoomType.HasValue)
{
housePredicate += h => h.MainRoom.Type == userSelectedRoomType.Value;
}
// MainRoom.Name = \"a Room\" and Rooms.Count = 3 or
// ?????????????????????????
var aRoomsHouses = houses.AsQueryable<House>().Where(housePredicate);
I tested this one, honest :)
what about this
var kitchens = houses
.SelectMany(h => h.Rooms, (h, r) => new {House = h, Room = r})
.Where(hr => hr.Room.Type == RoomType.Kitchen)
.Select(hr => hr.House);
To add a new Enum type to dynamic Linq, you must add the following code :
typeof(Enum),
typeof(T)
T : Enum type
in predefined types of dynamic. That works for me.
Related
Say I have the following class structures
public class EmailActivity {
public IEnumerable<MemberActivity> Activity { get; set; }
public string EmailAddress { get; set; }
}
public class MemberActivity {
public EmailAction? Action { get; set; }
public string Type { get; set; }
}
public enum EmailAction {
None = 0,
Open = 1,
Click = 2,
Bounce = 3
}
I wish to filter a list of EmailActivity objects based on the presence of a MemberActivity with a non-null EmailAction matching a provided list of EmailAction matches. I want to return just the EmailAddress property as a List<string>.
This is as far as I've got
List<EmailAction> activityTypes; // [ EmailAction.Open, EmailAction.Bounce ]
List<string> activityEmailAddresses =
emailActivity.Where(
member => member.Activity.Where(
activity => activityTypes.Contains(activity.Action)
)
)
.Select(member => member.EmailAddress)
.ToList();
However I get an error message "CS1503 Argument 1: cannot convert from 'EmailAction?' to 'EmailAction'"
If then modify activityTypes to allow null values List<EmailAction?> I get the following "CS1662 Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type".
The issue is the nested .Where it's returning a list, but the parent .Where requires a bool result. How would I tackle this problem?
I realise I could do with with nested loops however I'm trying to brush up my C# skills!
Using List.Contains is not ideal in terms of performance, HashSet is a better option, also if you want to select the email address as soon as it contains one of the searched actions, you can use Any:
var activityTypes = new HashSet<EmailAction>() { EmailAction.Open, EmailAction.Bounce };
List<string> activityEmailAddresses =
emailActivity.Where(
member => member.Activity.Any(
activity => activity.Action.HasValue &&
activityTypes.Contains(activity.Action.Value)
)
)
.Select(activity => activity.EmailAddress)
.ToList();
You want to use All or Any depends if you want each or at least one match...
HashSet<EmailAction> activityTypes = new HashSet<EmailAction> { EmailAction.None };
var emailActivity = new List<EmailActivity>
{
new EmailActivity { Activity = new List<MemberActivity>{ new MemberActivity { Action = EmailAction.None } }, EmailAddress = "a" },
new EmailActivity { Activity = new List<MemberActivity>{ new MemberActivity { Action = EmailAction.Click } }, EmailAddress = "b" }
};
// Example with Any but All can be used as well
var activityEmailAddresses = emailActivity
.Where(x => x.Activity.Any(_ => _.Action.HasValue && activityTypes.Contains(_.Action.Value)))
.Select(x => x.EmailAddress)
.ToArray();
// Result is [ "a" ]
Have a generic function, it doesn't really mater what it is doing (FYI coping one list of object to another), main idea is that it has two types Ts and Tp
public static List<Tp> CreateAndFillList<Ts, Tp>(this IEnumerable<Ts> sItems) where Tp : class, new()
{
Type myType = default(Type);
PropertyInfo[] pSourceAllInfos = null;
if (pSourceAllInfos == null)
{
myType = typeof(Ts);
pSourceAllInfos = myType.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToArray();
}
PropertyInfo[] pTargetAllInfos = null;
if (pTargetAllInfos == null)
{
myType = typeof(Tp);
pTargetAllInfos = myType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(pi => pi.CanWrite).ToArray();
}
var joinedPI = (from spi in pSourceAllInfos
join tpi in pTargetAllInfos on spi.Name.ToLower() equals tpi.Name.ToLower()
select new { spi, tpi }).ToList();
List<Tp> retList = new List<Tp>();
foreach (var sItem in sItems)
{
Tp tpNewItem = new Tp();
foreach (var jpi in joinedPI)
{
jpi.tpi.SetValue(tpNewItem, jpi.spi.GetValue(sItem, null), null);
}
retList.Add(tpNewItem);
}
return retList;
}
Have two simple classes
public class SourceInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string SourceData { get; set; }
}
public class TargetInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string TargetData { get; set; }
}
My problem is that following code throw compilation error
private void button1_Click(object sender, EventArgs e)
{
List<SourceInfo> srcLst = new List<SourceInfo>();
srcLst.Add(new SourceInfo() { Id = 1, Name = "First", SourceData = "data1" });
srcLst.Add(new SourceInfo() { Id = 2, Name = "Second", SourceData = "data2" });
var q = from li in srcLst
select new { li.Id, li.Name };
dynamic qD = from li in srcLst
select new { li.Id, li.Name };
var resultLst = srcLst.CreateAndFillList<TargetInfo>();
//Using the generic method 'ExtensionTest.Extensions.CreateAndFillList<Ts,Tp>(System.Collections.Generic.IEnumerable<Ts>)' requires 2 type arguments
var resultLst1 = q.CreateAndFillList<TargetInfo>();
//Using the generic method 'ExtensionTest.Extensions.CreateAndFillList<Ts,Tp>(System.Collections.Generic.IEnumerable<Ts>)' requires 2 type arguments
var resultLst2 = qD.CreateAndFillList<TargetInfo>();
//works but will have to use dynamic...
}
And at the same time in VB.Net everything is ok!!!!
Dim lst As List(Of SourceInfo) = New List(Of SourceInfo)()
lst.Add(New SourceInfo() With {.Id = 1, .Name = "First"})
lst.Add(New SourceInfo() With {.Id = 2, .Name = "Second"})
Dim q = From li In lst
Select New With {li.Id, li.Name}
Dim retLst = lst.CreateAndFillList(Of TargetInfo)()
Dim retLst1 = q.CreateAndFillList(Of TargetInfo)()
My problem is I don't want to use dynamic everywhere because it will require extra coding plus it is run-time compilation.
What I am doing wrong in C#? please help.
The compiler message is quite clear about the problem: You need to specify both type arguments. If you specify only one, it is unclear which of both parameters it should be.
var resultLst = srcLst.CreateAndFillList<SourceInfo, TargetInfo>();
var resultLst1 = q.CreateAndFillList<SourceInfo, TargetInfo>();
And this:
dynamic qD = from li in srcLst
select new { li.Id, li.Name };
does not need to be dynamic. var is more appropriate here and will give you compile-time errors. If you do this, you get the same error for qD:
var resultLst2 = qD.CreateAndFillList<SourceInfo, TargetInfo>();
Otherwise, you will get the error only at runtime.
What I am doing wrong in C#?
The compiler won't partially infer generic parameters. Using dynamic defers that check to runtime, where it will likely fail. You need to supply both the input and output generic parameters in your call to CreateAndFillList.
var resultLst = srcLst.CreateAndFillList<SourceInfo, TargetInfo>();
I want to merge two lists with different attributes into one list, but while merging it, I want to check if there is, in this particular example, exact date that is in the both lists, and if there is, I want to take both attributes from that elements, and merge them into one element in another list
List 1:
List<object> r1 = (from x in sp1 select new
{
x.Imported,
x.Period
}).ToList<object>();
L1 result:
List 2:
List<object> r2 = (from x in sp2 select new
{
x.Dissolution,
x.Period
}).ToList<object>();
L2 result:
Wanted result:
For now, this is how i merge r1 and r2:
List<object> r3 = new List<object>(r1.Concat(r2));
You could transform them into same type and use stuff like this
r1
.Select(x => new { Imported = x.Imported, Dissolution = null, Period = x.Period)
.Concat(
r2.Select(x => new { Imported = null, Dissolution = x.Dissolution, Period = x.Period))
.GroupBy(x => x.Period)
.Select(x => new { Imported = x.Max(e => e.Imported),
Dissolution = x.Max(e => e.Dissolution),
Period = x.Key);
Create a Dictionary
Dictionary MyDict<String, List<Object>>;
MyDict[object.Perdiod].Add(object);
For each date there will be an entry in the dictionnary, and it will at this "date index" keep a list of all object that happens at that period.
Easiest way IMO and it does not need to make a O(n) check for every entry added
Just make sure when you add data that it is not null IE
MyDict[Object.Period] != null
Plus, has Nikhil Agrawal said I would not use Object to keep a list of thing... it feels wrong and is error prone. you might want to declare an abstract class that will be used like an Interface or simply an interface for those items (object).
AFAK you need reflection to achieve this as the name for Anonymous type is assigned at Compile time here an example on how you can achieve what you want
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<ImportedType> sp1 = new List<ImportedType>();
List<DissolutionType> sp2 = new List<DissolutionType>();
sp1.AddRange( new ImportedType[]{new ImportedType() { Imported = 2, Period = "2024-02" }, new ImportedType() { Imported = 2, Period = "2014-11" }, new ImportedType() { Imported = 2, Period = "2024-12" }});
sp2.AddRange(new DissolutionType[] { new DissolutionType() { Dissolution = 2, Period = "2024-02" }, new DissolutionType() { Dissolution = 2, Period = "2034-02" }, new DissolutionType() { Dissolution = 2, Period = "2024-12" } });
var r1 = (from x in sp1
select new
{
x.Imported,
x.Period
}).ToList<object>();
var r2 = (from x in sp2
select new
{
x.Dissolution,
x.Period
}).ToList<object>();
var r3 = r1.Concat(r2).Except(r1.Where(res =>
{
object vp2 = r2.SingleOrDefault(res2 => GetValue(res2) == GetValue(res));
if (vp2!=null)
{
return true;
}
return false;
}));
}
private static object GetValue(object res)
{
Type t = res.GetType();
PropertyInfo p = t.GetProperty("Period");
object v = p.GetValue(res, null);
return v;
}
}
}
//here I suppose that you implements 2 classes like this
public class ImportedType
{
public int Imported { get; set; }
public string Period { get; set; }
}
public class DissolutionType
{
public int Dissolution { get; set; }
public string Period { get; set; }
}
Result
I agree with Nikhil Agrawal in that the code now really needs some fixing up, as it is really hard to work with anonymous types, especially since they have been cast to object.
Ignoring that, and accepting it as a challenge (use anonymous types cast to object), this is what I have come up with:
Code that merges does a full outer join:
Func<object, object> getPeriodKey = first =>
{
var periodProperty = first.GetType().GetProperty("Period");
return periodProperty.GetValue(first);
};
var temp = r1.GroupJoin(r2, getPeriodKey, getPeriodKey, (obj, tInner) =>
{
dynamic right = tInner.FirstOrDefault();
if (right == null)
return (object)(new
{
Period = ((dynamic)obj).Period,
Imported = ((dynamic)obj).Imported,
});
else
return (object)(new
{
Period = ((dynamic)obj).Period,
Imported = ((dynamic)obj).Imported,
Dissolution = (int?)right.Dissolution,
});
});
var merged = temp.Union(r2, new RComparer());
and the required comparer is below:
class RComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
var xPeriodProperty = x.GetType().GetProperty("Period");
var yPeriodProperty = y.GetType().GetProperty("Period");
if (xPeriodProperty != null && yPeriodProperty != null)
{
var xPeriod = (string)xPeriodProperty.GetValue(x);
var yPeriod = (string)yPeriodProperty.GetValue(y);
return xPeriod == yPeriod;
}
else
return base.Equals(y);
}
public int GetHashCode(object obj)
{
var periodProperty = obj.GetType().GetProperty("Period");
if (periodProperty != null)
//This will essentially hash the string value of the Period
return periodProperty.GetValue(obj).GetHashCode();
else
return obj.GetHashCode();
;
}
}
I am trying to figure out the best way to organise a bunch of my data classes, given I need to be able to access some metrics on them all at some point.
Here's a snippet of my OR class:
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
}
Not every OR I initialise will have values for all properties. I want to be able to 'collect' thousands of these together in such a way that I can easily obtain a count of how many OR objects had a value set. For example:
OR a = new OR() { reference = "a" }
OR b = new OR() { reference = "b", title = "test" }
OR c = new OR() { reference = "c", title = "test", status = status.CLOSED }
Now these are somehow collected in such a way I can do (pseudo):
int titleCount = ORCollection.titleCount;
titleCount = 2
I would also want to be able gather metrics for the enum type properties, for example retrieve a Dictionary from the collection that looks like:
Dictionary<string, int> statusCounts = { "CLOSED", 1 }
The reason for wanting access to these metrics is that I am building two collections of ORs and comparing them side-by-side for any differences (they should be identical). I want to be able to compare their metrics at this higher level first, then break-down where precisely they differ.
Thanks for any light that can be shed on how to accomplish this. :-)
... to 'collect' thousands of these
Thousands is not a huge number. Just use a List<OR> and you can get all your metrics with Linq queries.
For example:
List<OR> orList = ...;
int titleCount = orList
.Where(o => ! string.IsNullOrEmpty(o.title))
.Count();
Dictionary<status, int> statusCounts = orList
.GroupBy(o => o.status)
.ToDictionary(g => g.Key, g => g.Count());
The existing answers using Linq are absolutely great and really elegant, so the idea presented below is just for posterity.
Here is a (very rough) reflection-based program that will alow you to count the "valid" properties in any collection of objects.
The validators are defined by you in the Validators dictionary so that you can easily change what is a valid/invalid value for each property. You may find it useful as a concept if you end up with objects having tons of properties and don't want to have to write inline linq metrics on the actual collection itself for every single property.
You could weaponise this as a function and then run it against both collections, giving you a basis to report on the exact differences between both since it records the references to the individual objects in the final dictionary.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace reftest1
{
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
public int foo { get; set; }
}
//creates a dictionary by property of objects whereby that property is a valid value
class Program
{
//create dictionary containing what constitues an invalid value here
static Dictionary<string,Func<object,bool>> Validators = new Dictionary<string, Func<object,bool>>
{
{"reference",
(r)=> { if (r ==null) return false;
return !String.IsNullOrEmpty(r.ToString());}
},
{"title",
(t)=> { if (t ==null) return false;
return !String.IsNullOrEmpty(t.ToString());}
},
{"status", (s) =>
{
if (s == null) return false;
return !String.IsNullOrEmpty(s.ToString());
}},
{"foo",
(f) =>{if (f == null) return false;
return !(Convert.ToInt32(f.ToString()) == 0);}
}
};
static void Main(string[] args)
{
var collection = new List<OR>();
collection.Add(new OR() {reference = "a",foo=1,});
collection.Add(new OR(){reference = "b", title = "test"});
collection.Add(new OR(){reference = "c", title = "test", status = status.CLOSED});
Type T = typeof (OR);
var PropertyMetrics = new Dictionary<string, List<OR>>();
foreach (var pi in GetProperties(T))
{
PropertyMetrics.Add(pi.Name,new List<OR>());
foreach (var item in collection)
{
//execute validator if defined
if (Validators.ContainsKey(pi.Name))
{
//get actual property value and compare to valid value
var value = pi.GetValue(item, null);
//if the value is valid, record the object into the dictionary
if (Validators[pi.Name](value))
{
var lookup = PropertyMetrics[pi.Name];
lookup.Add(item);
}
}//end trygetvalue
}
}//end foreach pi
foreach (var metric in PropertyMetrics)
{
Console.WriteLine("Property '{0}' is set in {1} objects in collection",metric.Key,metric.Value.Count);
}
Console.ReadLine();
}
private static List<PropertyInfo> GetProperties(Type T)
{
return T.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
}
}
}
You can get the title count using this linq query:
int titleCount = ORCollection
.Where(x => !string.IsNullOrWhiteSpace(x.title))
.Count();
You could get the count of closed like this:
int closedCount = ORCollection
.Where(x => x.status == status.CLOSED)
.Count();
If you were going to have larger collections or you access the values a lot it might be worth creating a custom collection implementation that stores the field counts, it could then increment/decrement these values as you add and remove items. You could also store a dictionary of status counts in this custom collection that gets updated as you add and remove items.
Is it possible to make a template for SELECT in a LINQ query? Right now I have 6 methods that uses the exact same SELECT, i would like to use a template if possible.
This is the code I'm using, when I want to make a change to the select I have to change the same thing at so many places in my code.
result = query.Select(b => new
{
route_id = b.b.route_id,
name = b.b.name,
description = b.b.description,
distance = b.b.distance,
distance_to_route = (int)b.distance_to_from_me,
departure_place = b.b.departure_place,
arrival_place = b.b.arrival_place,
owner = b.b.user.username,
average_rating = b.avg_rating,
is_favorite = b.is_favorite,
date = b.b.date,
attributes = b.b.route_attributes.Select(c =>
c.route_attribute_types.attribute_name),
coordinates = b.b.coordinates.Select(c =>
new coordinateToSend { sequence = c.sequence,
lat = c.position.Latitude,
lon = c.position.Longitude })
});
Here is a simple example of one way you could do this:
In your example, you're converting the source type to an anonymous type. You could create a class to represent your converted/result type, for example:
public class ResultClass
{
public string ResultPropA { get; set; }
}
For examples sake, lets say the following was the definition of your source class:
public class SourceClass
{
public string SourcePropA { get; set; }
}
Now that you have type definitions for your source and result objects, you can create an extension method to convert a collection of your source class to a collection of your result class:
public static class SourceToResultRepository
{
public static IEnumerable<ResultClass> ConvertSourceToResult
(this IEnumerable<SourceClass> source)
{
return source.Select(s => new ResultClass
{
ResultPropA = s.SourcePropA
//Add all other property transformations here
});
}
}
And here is an example of how you could use it wherever you need to perform the transformation:
//Extension usage:
var result = Database.Source.ConvertSourceToResult();
//Direct usage:
var result = SourceToResultRepository.ConvertSourceToResult(Database.Source);