Method to Compare objects - c#

I am working on a method that can pass in 2 objects of the same type and it will compare the two. In the end I am planning on overloading it to accept property names etc to evaluate only those or exclude only those form the evaluation. That will have to happen after I get the basic full compare working.
I have be been able to compare value types and reference types. Where I am stuck is being able to compare properties that are Enumerable. The else statement is what I cannot get to work correctly.
public static void CompareObjects<T>(T obj1, T obj2)
{
foreach (var property in typeof(T).GetProperties())
{
var obj1Prop = property.GetValue(obj1);
var obj2Prop = property.GetValue(obj2);
//for value types
if (property.PropertyType.IsPrimitive || property.PropertyType.IsValueType || property.PropertyType == typeof(string))
{
if (obj1Prop != obj2Prop)
Console.WriteLine($"Object 1 {property.Name}:{obj1Prop}{Environment.NewLine}Object 2 {property.Name}:{obj2Prop}{Environment.NewLine}");
}
//for objects
else if (property.PropertyType.IsClass && !typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
dynamic obj1PropObj = Convert.ChangeType(obj1Prop, property.PropertyType);
dynamic obj2PropObj = Convert.ChangeType(obj2Prop, property.PropertyType);
CompareObjects(obj1PropObj, obj2PropObj);
}
//for Enumerables
else
{
var enumerablePropObj1 = property.GetValue(obj1) as IEnumerable;
var enumerablePropObj2 = property.GetValue(obj2) as IEnumerable;
if (enumerablePropObj1 == null) continue;
if (enumerablePropObj2 == null) continue;
var list1 = enumerablePropObj1.GetEnumerator();
var list2 = enumerablePropObj2.GetEnumerator();
while (list1.MoveNext())
{
list2.MoveNext();
CompareObjects(list1.Current, list2.Current);
}
}
}
}
This iterates through the list but without the values. The setup that I am using to implement this is as follows:
static void Main(string[] args)
{
var student1 = new Student
{
FirstName = "John",
LastName = "Smith",
Age = 18,
DateOfBirth = DateTime.Parse("1989/03/03"),
Job = new Job
{
Company = "CompanyOne",
Title = "Supervisor"
},
PhoneNumbers = new List<PhoneNumber>
{
new PhoneNumber
{
Number = "8675309",
Label = "Home"
},
new PhoneNumber
{
Number = "1234567",
Label = "Work"
}
}
};
var student2 = new Student
{
FirstName = "Theodore",
LastName = "Smith",
Age = 22,
DateOfBirth = DateTime.Parse("1990/03/03"),
Job = new Job
{
Company = "CompanyOne",
Title = "Manager"
},
PhoneNumbers = new List<PhoneNumber>
{
new PhoneNumber
{
Number = "8675308",
Label = "Home"
},
new PhoneNumber
{
Number = "1234567",
Label = "Work"
}
}
};
CompareObjects(student1, student2);
Console.WriteLine("Done");
Console.ReadKey();
}

Your problem is that your method is generic. When you call it on the sub-collections, the type you are using for the comparison is System.Object, which of course has no interesting properties to compare.
Change your method declaration and the preamble of the method:
public static void CompareObjects(object obj1, object obj2)
{
if (obj1.GetType() != obj2.GetType())
{
return;
}
foreach (var property in obj1.GetType().GetProperties())
{
...
Note that with this approach, you also do not need the dynamic and ChangeType() code.
Finally, you may want to consider changing your inequality comparison so that instead of using the != operator it uses the Equals() method:
if (!obj1Prop.Equals(obj2Prop))
{
Console.WriteLine($"Object 1 {property.Name}:{obj1Prop}{Environment.NewLine}Object 2 {property.Name}:{obj2Prop}{Environment.NewLine}");
}
Even when the method was generic, that would be a good idea; while one is supposed to implement the equality and inequality operators if one overrides Equals(), that doesn't always happen. When the method is non-generic, of course you won't even get operator overloads for the specific types (at least not without a lot of extra work).
(I guess you could instead of the above use the same dynamic/ChangeType() approach you use for the non-enumerable properties, using e.g. list1.Current.GetType() as the type instead of property.PropertyType; that could allow the method to remain generic. But there's so much extra overhead to dynamic and ultimately it will all come back down to executing the same code, I just don't see the point in doing it that way.)

Related

Removing code duplication and redundancy based on different criteria and priority level

I was wondering if I could get your suggestions on how to remove the code duplication/repetition here.
Any preferred and correct way of doing so?
Summary: This function returns a car's name and accepts the car code as a parameter
public string GetMatchingCar(string carCode)
{
//CarResults is the list of cars that consists the details of the car as a collection.
var carResults = new List<Car>
{
new Car{Name="AB car", Code="AB", Colors = new List<string>{"blue","green","black"}, Features=new List<string>{"compact","fast","light"} },
new Car{Name="AC car", Code="AC", Colors = new List<string>{"gray","white","yellow"}, Features=new List<string>{"extended","fast","heavy"} },
new Car{Name="DE car", Code="DE", Colors = new List<string>{"red","green","purple"}, Features=new List<string>{"sports","light"} },
//and so on
};
//Specifications is a list of specification to choose from that includes color and feature.
var specifications = new List<Specification>
{
new Specification{Color="blue", Feature="heavy"},
new Specification{Color="red", Feature="light"},
new Specification{Color="maroon", Feature="compact"},
new Specification{Color="black", Feature="manual"},
new Specification{Color="neon", Feature="heavy"},
//and so on
}
//Now, we need to return the car based on the priority of criteria. Say P1, P2 and P3. P1's priority being highest.
//P1: Code + Color + Feature
//P2: Code + Feature
//P3: Feature
//just for example, priorities can be from P1 - P7
//Now we have to combine the specification with carCode to check the priorities and return the matching car.
//TODO: Here, is where I think I am doing wrong by having each for loop for each priority (as priorities can be upto Seven). All the foreach loop won't be executed if a priority is matched and returns a car.
//TODO: Wondering how could I improve this?
//priority P1: Code + Color + Feature
foreach(var specification in specifications){
var matchingCarP1 = carResults.FirstOrDefault(x=> x.Code.Equals(carCode) && x.Colors.Contains(specification.Color) && x.Features.Contains(specification.Feature));
if(matchingCarP1 != null) return matchingCarP1.Name;
}
//priority P2: Code + Feature
foreach(var specification in specifications){
var matchingCarP2 = carResults.FirstOrDefault(x=> x.Code.Equals(carCode) && x.Features.Contains(specification.Feature));
if(matchingCarP2 != null) return matchingCarP2.Name;
}
//priority P3: Feature
foreach(var specification in specifications){
var matchingCarP3 = carResults.FirstOrDefault(x.Features.Contains(specification.Feature));
if(matchingCarP3 != null) return matchingCarP3.Name;
}
//Other priorities
return string.Empty;
}
Any suggestions or feedback on this would be really helpful and highly appreciated!
Thank you!
In my view, this method has too many responsibilies and it is too long. Let's try to apply Single Responsibility Principle of SOLID.
So let's create a method with one goal. The goal or responsibility will be just to get cars:
private List<Car> GetCars() =>
new List<Car>
{
new Car{Name="AB car", Code="AB", Colors =
new List<string>{"blue","green","black"},
Features=new List<string>{"compact","fast","light"} },
new Car{Name="AC car", Code="AC", Colors =
new List<string>{"gray","white","yellow"},
Features=new List<string>{"extended","fast","heavy"} },
new Car{Name="DE car", Code="DE", Colors =
new List<string>{"red","green","purple"},
Features=new List<string>{"sports","light"} },
//and so on
};
and
private List<Car> GetSpecifications() =>
new List<Specification>
{
new Specification{Color="blue", Feature="heavy"},
new Specification{Color="red", Feature="light"},
new Specification{Color="maroon", Feature="compact"},
new Specification{Color="black", Feature="manual"},
new Specification{Color="neon", Feature="heavy"},
//and so on
}
And it looks like that we can avoid unnecessary iteration of the whole specifications by adding nested if statements:
private string FindMatchingCar(List<Car> cars,
List<Specification> specifications)
{
foreach(var specification in specifications){
var matchingCarP1 = carResults.FirstOrDefault(x=> x.Code.Equals(carCode)
&& x.Colors.Contains(specification.Color)
&& x.Features.Contains(specification.Feature));
if(matchingCarP1 != null) return matchingCarP1.Name;
var matchingCarP2 = carResults.FirstOrDefault(x=> x.Code.Equals(carCode)
&& x.Features.Contains(specification.Feature));
if(matchingCarP2 != null) return matchingCarP2.Name;
var matchingCarP3 = carResults.FirstOrDefault(x =>
x.Features.Contains(specification.Feature));
if(matchingCarP3 != null) return matchingCarP3.Name;
}
return string.Empty;
}
And then your method will look like this:
public string GetMatchingCar(string carCode)
{
var cars = GetCars();
var specifications = GetSpecifications();
return FindMatchingCar(cars, specifications);
}
Based on the criteria, and each of them being different plus the specifications being a list, in my opinion what you have is okay.
p.s. pls treat as a comment

Dynamic LINQ Sub Query

I have LINQ query that I want to generate dynamically:
var groupData =
from l in data
group l by l.Field1 into field1Group
select new MenuItem()
{
Key = field1Group.Key,
Count = field1Group.Count(),
Items = (from k in field1Group
group k by k.Field2 into field2Group
select new MenuItem()
{
Key = field2Group.Key,
Count = field2Group.Count()
}).ToList()
};
The ultimate goal is to be able to dynamically group the data by any combination of fields with no limit on the nested queries.
I can get as far as the first level but I'm struggling with the nested sub queries:
string field1 = "Field1";
string field2 = "Field2";
var groupDataD =
data.
GroupBy(field1, "it").
Select("new ( it.Key, it.Count() as Count )");
Is this possible with chained dynamic LINQ? Or is there a better way to achieve this?
The following should work (though personally I would rather avoid using such code):
Follow this answer to add the following in ParseAggregate, :
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
// Change starts here
var originalIt = it;
var originalOuterIt = outerIt;
// Change ends here
outerIt = it;
ParameterExpression innerIt = Expression.Parameter(elementType, elementType.Name);
it = innerIt;
Expression[] args = ParseArgumentList();
// Change starts here
it = originalIt;
outerIt = originalOuterIt;
// Change ends here
...
}
Add Select, GroupBy, ToList into IEnumerableSignatures, and respective conditions in ParseAggregate, as explained in this answer:
interface IEnumerableSignatures
{
...
void GroupBy(object selector);
void Select(object selector);
void ToList();
...
}
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
...
if (signature.Name == "Min" ||
signature.Name == "Max" ||
signature.Name == "GroupBy" ||
signature.Name == "Select")
...
}
Finally, Your query would be:
string field1 = "Field1";
string field2 = "Field2";
var result =
data
.GroupBy(field1, "it")
.Select($#"new (
it.Key,
it.Count() as Count,
it.GroupBy({field2})
.Select(new (it.Key, it.Count() as Count))
.ToList() as Items
)");
Note that "it" holds a different instance when used in the parent query vs. the subquery. I tried to take advantage of "outerIt" to overcome this conflation, but unfortunately without success (but maybe you'd succeed? maybe 1, 2 would help)
A simple example for future reference:
public class Person
{
public string State { get; set; }
public int Age { get; set; }
}
public static Main()
{
var persons = new List<Person>
{
new Person { State = "CA", Age = 20 },
new Person { State = "CA", Age = 20 },
new Person { State = "CA", Age = 30 },
new Person { State = "WA", Age = 60 },
new Person { State = "WA", Age = 70 },
};
var result = persons
.GroupBy("State", "it")
.Select(#"new (
it.Key,
it.Count() as Count,
it.GroupBy(Age)
.Select(new (it.Key, it.Count() as Count))
.ToList() as Items
)");
foreach (dynamic group in result)
{
Console.WriteLine($"Group.Key: {group.Key}");
foreach (dynamic subGroup in group.Items)
{
Console.WriteLine($"SubGroup.Key: {subGroup.Key}");
Console.WriteLine($"SubGroup.Count: {subGroup.Count}");
}
}
}

Linq/C#: How to split List into variable length chunks based on list item information?

I am trying to split up a list of type Record in Linq into sub lists based on certain Type information. There is always one record with type "a" before and one with type "b" after each group of records. I have a class Record:
class Record
{
public string Type { get; set; }
public string SomeOtherInformation { get; set; }
}
Here is a sample list (List<Record> records):
Type SomeOtherInformation
a ......
x ......
x ......
b ......
a ......
b ......
a ......
x ......
x ......
x ......
x ......
x ......
b ......
The desired output is (List<List<Record>> lists):
List #1: List #2: List #3:
a ...... a ...... a ......
x ...... b ...... x ......
x ...... x ......
b ...... x ......
x ......
x ......
b ......
I am currently going through this list with a for loop and create a new list whenever the type is "a" and add it to the sub-list list when an item's type is "b". I am wondering if there is a better way to to this with Linq. Can this be done with Linq, if so, how?
You can't cleanly do this with normal LINQ, as far as I'm aware. The streaming operators within LINQ rely on you being able to make a decision about an item (e.g. whether or not to filter it, how to project it, how to group it) based on just that item, and possibly its index within the original source. In your case, you really need more information than that - you need to know how many b items you've already seen.
You could do it like this:
int bs = 0;
var groups = records.GroupBy(item => item.Type == 'b' ? bs++ : bs,
(key, group) => group.ToList())
.ToList();
However, that relies on the side-effect of b++ within the grouping projection (to keep track of how many b items we've already seen) - it's definitely not idiomatic LINQ, and I wouldn't recommend it.
I would use an extension method for this instead:
public static IEnumerable<IEnumerable<TSource>> SplitItems<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> startItem,
Func<TSource, bool> endItem)
{
var tempList = new List<TSource>();
int counter = 0;
foreach (var item in source)
{
if (startItem(item) || endItem(item)) counter++;
tempList.Add(item);
if (counter%2 == 0)
{
yield return tempList;
tempList = new List<TSource>();
}
}
}
Here is the usage:
var result = list.SplitItems(x => x.Type == "a", x => x.Type == "b").ToList();
This will return you a List<IEnumerable<Record>> with 3 items.Ofcourse the method assumes at least there is one start item in the beginning and the end item at the end.You may want to add some checks and improve it according to your requirements.
Definitely not pure LINQ, but I could imagine using TakeWhile in a loop to do this:
List<Record> data;
List<List<Record>> result = new List<List<Record>>();
IEnumerable<Record> workingData = data;
while (workingData.Count() > 0)
{
IEnumerable<Record> subList = workingData.Take(1).Concat(workingData.Skip(1).TakeWhile(c => c.Type != 'a'));
result.Add(subList.ToList());
workingData = workingData.Except(subList);
}
To explain, we get the 'a' we know is at the start of our sequence, then skip it and take until we encounter another 'a'. This makes up one of the sub records, so we add it to our result. Then we remove this subList from our "working" set, and enumerate again until we run out of elements.
I'm not sure this would be better than your existing solution, but hopefully it helps!
This actually does work, (tested on VS 2013, .NET 4.5.1) by using workingData instead of data in the loop (a typo on my part, fixed above). Except will use the default comparer for comparing the objects, since we don't override .Equals, it will compare the references (effectively the pointers). Thus, duplicate data is not a problem. If .Equals were overridden, you would need to ensure each record was unique.
If anyone would like to verify this, here is my test program (just put a breakpoint on the Console.ReadKey, you'll see result has the correct data):
class Program
{
static void Main(string[] args)
{
List<Record> testData = new List<Record>()
{
new Record() { Type = 'a', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'b', Data="Data" },
new Record() { Type = 'a', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'b', Data="Data" },
new Record() { Type = 'a', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'b', Data="Data" }
};
List<List<Record>> result = new List<List<Record>>();
IEnumerable<Record> workingData = testData;
while (workingData.Count() > 0)
{
IEnumerable<Record> subList = workingData.Take(1).Concat(workingData.Skip(1).TakeWhile(c => c.Type != 'a'));
result.Add(subList.ToList());
workingData = workingData.Except(subList);
}
Console.ReadKey();
}
}
class Record
{
public char Type;
public String Data;
}
As already mentioned, this is not a case that LINQ handles well because you can only really make decisions based on the current item, not what was previously seen. You need to maintains some kind of state to keep track of the groupings. Relying on side effects
Writing your own extension method would be the better option. You can keep state and make it all self contained (much like the existing operators such as GroupBy() and others). Here's an implementation I have that can optionally include items that are not contained within the start and end items.
public static IEnumerable<IImmutableList<TSource>> GroupByDelimited<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> startDelimiter,
Func<TSource, bool> endDelimiter,
bool includeUndelimited = false)
{
var delimited = default(ImmutableList<TSource>.Builder);
var undelimited = default(ImmutableList<TSource>.Builder);
foreach (var item in source)
{
if (delimited == null)
{
if (startDelimiter(item))
{
if (includeUndelimited && undelimited != null)
{
yield return undelimited.ToImmutable();
undelimited = null;
}
delimited = ImmutableList.CreateBuilder<TSource>();
}
else if (includeUndelimited)
{
if (undelimited == null)
{
undelimited = ImmutableList.CreateBuilder<TSource>();
}
undelimited.Add(item);
}
}
if (delimited != null)
{
delimited.Add(item);
if (endDelimiter(item))
{
yield return delimited.ToImmutable();
delimited = null;
}
}
}
}
However, if you really wanted to, you can still do this using LINQ operators (Aggregate()) but it will not be a real LINQ solution. It will again, look like a self contained foreach loop.
var result = records.Aggregate(
Tuple.Create(default(List<Record>), new List<List<Record>>()),
(acc, record) =>
{
var grouping = acc.Item1;
var result = acc.Item2;
if (grouping == null && record.Type == "a")
{
grouping = new List<Record>();
}
if (grouping != null)
{
grouping.Add(record);
if (record.Type == "b")
{
result.Add(grouping);
grouping = null;
}
}
return Tuple.Create(grouping, result);
},
acc => acc.Item2
);

Selecting from set of enum values

I have collection of items which are having one enum property list.
Original property looks like
public class Content {
List<State> States {get; set;}
}
where 'State' is enum with almost 15 options.
While iterating collection of Content objects, I want to check it States property has certain values like State.Important and State.Updated exists in States and set another string from it.
something like
if(item.States.Has(State.Important) && item.States.Has(State.Updated))
string toProcess = "Do";
How to do this using Linq or Lambda ?
This should work if you must use Linq:
if (item.States.Any(state => state == State.Important) && item.States.Any(state => state == State.Updated))
Otherwise just use Contains() like #ElRonnoco says.
(However if your states are flags (powers of 2), then this answer will be slightly different.)
The trouble with this approach is that it iterates over the collection fully twice if neither of the states are set. If this happens often, it will be slower than it could be.
You can solve it without linq in a single pass like so:
bool isUpdated = false;
bool isImportant = false;
foreach (var state in item.States)
{
if (state == State.Important)
isImportant = true;
else if (state == State.Updated)
isUpdated = true;
if (isImportant && isUpdated)
break;
}
if (isImportant && isUpdated)
{
// ...
}
This is unlikely to be an issue unless you have very large lists which often don't have either of the target states set, so you're probably best off using El Ronnoco's solution anyway.
If you have a lot of states to deal with, you could simplify things by writing an extension method like so:
public static class EnumerableExt
{
public static bool AllPredicatesTrueOverall<T>(this IEnumerable<T> self, params Predicate<T>[] predicates)
{
bool[] results = new bool[predicates.Length];
foreach (var item in self)
{
for (int i = 0; i < predicates.Length; ++i)
if (predicates[i](item))
results[i] = true;
if (results.All(state => state))
return true;
}
return false;
}
I had some difficulty coming up for a name for this. It will return true if for each predicate there is at least one item in the sequence for which the predicate is true. But that's a bit long for a method name... ;)
Then your example would become:
if (item.States.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Here's some sample code that uses it:
enum State
{
Unknown,
Important,
Updated,
Deleted,
Other
}
void run()
{
IEnumerable<State> test1 = new[]
{
State.Important,
State.Updated,
State.Other,
State.Unknown
};
if (test1.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Console.WriteLine("test1 passes.");
else
Console.WriteLine("test1 fails.");
IEnumerable<State> test2 = new[]
{
State.Important,
State.Other,
State.Other,
State.Unknown
};
if (test2.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Console.WriteLine("test2 passes.");
else
Console.WriteLine("test2 fails.");
// And to show how you can use any number of predicates:
bool result = test1.AllPredicatesTrueOverall
(
state => state == State.Important,
state => state == State.Updated,
state => state == State.Other,
state => state == State.Deleted
);
}
But perhaps the easiest is to write an extension method for IEnumerable<State> (if you only have the one state enum to worry about):
public static class EnumerableStateExt
{
public static bool AllStatesSet(this IEnumerable<State> self, params State[] states)
{
bool[] results = new bool[states.Length];
foreach (var item in self)
{
for (int i = 0; i < states.Length; ++i)
if (item == states[i])
results[i] = true;
if (results.All(state => state))
return true;
}
return false;
}
}
Then your original code will become:
if (item.States.AllStatesSet(State.Important, State.Updated))
and you can easily specify more states:
if (item.States.AllStatesSet(State.Important, State.Updated, State.Deleted))
You don't need Linq. I don't thinq
if(item.States.Contains(State.Important) && item.States.Contains(State.Updated))
string toProcess = "Do";
http://msdn.microsoft.com/en-us/library/bhkz42b3.aspx
List has a Contains method, so your code would be
if(item.States.Contains(State.Important) && item.States.Contains(State.Updated))
string toProcess = "Do";
I see no real benefit in using Linq or a lambda expression here...
You could go with
!(new List<States>{State.Important, State.Updated}.Except(item.States).Any());
It's not really shorter, but easier if you have a huge number of states to check.
As long as you want to check that the item has all states needed, you just have to add new States to the first list.
var res = (from items in item
where items.States.Has(State.Important) && items.States.Has(State.Updated)
select new { NewProcess = "Do" }).ToList();
foreach (var result in res)
{
string result = result.NewProcess
}
Try this
Maybe you could consider using your enum as a set of flags, i.e. you can combine multiple states without having a list:
[Flags]
public enum State
{
Important = 1,
Updated = 2,
Deleted = 4,
XXX = 8
....
}
public class Content
{
public State MyState { get; set; }
}
if ((myContent.MyState & State.Important) == State.Important
&& (myContent.MyState & State.Updated) == State.Updated)
{
// Important AND updated
}
Some sort of following implementation
Content obj = new Content();
obj.States = SomeMethod();
if(obj.States.Any(h => h == State.Important) && obj.States.Any(h => h == State.Updated))
{
string toProcess = "Do";
}

Dynamic Linq - Perform a query on an object with members of type "dynamic"

I am trying to use a dynamic linq query to retrieve an IEnumerable<T> from an object collection (Linq to Object), each of the objects in the collection have an internal collection with another set of objects where the data is stored, these values are accessed through an indexer from the outer collection
The dynamic linq query returns the filtered set as expected when you are working with strongly typed objects but my object stores the data in a member of type dynamic, please see the example below:
public class Data
{
public Data(string name, dynamic value)
{
this.Name = name;
this.Value = value;
}
public string Name { get; set; }
public dynamic Value { get; set; }
}
public class DataItem : IEnumerable
{
private List<Data> _collection;
public DataItem()
{ _collection = new List<Data>(); }
public dynamic this[string name]
{
get
{
Data d;
if ((d = _collection.FirstOrDefault(i => i.Name == name)) == null)
return (null);
return (d.Value);
}
}
public void Add(Data data)
{ _collection.Add(data); }
public IEnumerator GetEnumerator()
{
return _collection.GetEnumerator();
}
}
public class Program
{
public void Example()
{
List<DataItem> repository = new List<DataItem>(){
new DataItem() {
new Data("Name", "Mike"),
new Data("Age", 25),
new Data("BirthDate", new DateTime(1987, 1, 5))
},
new DataItem() {
new Data("Name", "Steve"),
new Data("Age", 30),
new Data("BirthDate", new DateTime(1982, 1, 10))
}
};
IEnumerable<DataItem> result = repository.AsQueryable<DataItem>().Where("it[\"Age\"] == 30");
if (result.Count() == 1)
Console.WriteLine(result.Single()["Name"]);
}
When I run the above example I get: Operator '==' incompatible with operand types 'Object' and 'Int32'
Are dynamic members incompatible with Dynamic Linq queries?, or is there another way of constructing expressions that would evaluate properly when dealing with members of type dynamic
Thanks a lot for your help.
Are dynamic members incompatible with Dynamic Linq queries?, or is there another way of constructing expressions that would evaluate properly when dealing with members of type dynamic?
Both can work together. Just do a conversion to Int32 before doing the comparison like so:
IEnumerable<DataItem> result =
repository.AsQueryable<DataItem>().Where("Int32(it[\"Age\"]) == 30");
Edit 1: Having said that, the use of dynamic binding in connection with Linq is restricted in general as dynamic operations are not allowed in expression trees. Consider the following Linq-To-Objects query:
IEnumerable<DataItem> result = repository.AsQueryable().
Where(d => d["Age"] == 30);
This code snippet won't compile for the reason mentioned above.
Edit 2: In your case (and in conjunction with Dynamic Linq) there are some ways to hack yourself around the issues mentioned in Edit 1 and in the original question. For example:
// Variant 1: Using strings all the way
public void DynamicQueryExample(string property, dynamic val)
{
List<DataItem> repository = new List<DataItem>(){
new DataItem() {
new Data("Name", "Mike"),
new Data("Age", 25),
new Data("BirthDate", new DateTime(1987, 1, 5))
},
new DataItem() {
new Data("Name", "Steve"),
new Data("Age", 30),
new Data("BirthDate", new DateTime(1982, 1, 10))
}
};
// Use string comparison all the time
string predicate = "it[\"{0}\"].ToString() == \"{1}\"";
predicate = String.Format(whereClause , property, val.ToString());
var result = repository.AsQueryable<DataItem>().Where(predicate);
if (result.Count() == 1)
Console.WriteLine(result.Single()["Name"]);
}
Program p = new Program();
p.DynamicQueryExample("Age", 30); // Prints "Steve"
p.DynamicQueryExample("BirthDate", new DateTime(1982, 1, 10)); // Prints "Steve"
p.DynamicQueryExample("Name", "Mike"); // Prints "Steve" (nah, just joking...)
or:
// Variant 2: Detecting the type at runtime.
public void DynamicQueryExample(string property, string val)
{
List<DataItem> repository = new List<DataItem>(){
new DataItem() {
new Data("Name", "Mike"),
new Data("Age", 25),
new Data("BirthDate", new DateTime(1987, 1, 5))
},
new DataItem() {
new Data("Name", "Steve"),
new Data("Age", 30),
new Data("BirthDate", new DateTime(1982, 1, 10))
}
};
string whereClause = "{0}(it[\"{1}\"]) == {2}";
// Discover the type at runtime (and convert accordingly)
Type type = repository.First()[property].GetType();
string stype = type.ToString();
stype = stype.Substring(stype.LastIndexOf('.') + 1);
if (type.Equals(typeof(string))) {
// Need to surround formatting directive with ""
whereClause = whereClause.Replace("{2}", "\"{2}\"");
}
string predicate = String.Format(whereClause, stype, property, val);
var result = repository.AsQueryable<DataItem>().Where(predicate);
if (result.Count() == 1)
Console.WriteLine(result.Single()["Name"]);
}
var p = new Program();
p.DynamicQueryExample("Age", "30");
p.DynamicQueryExample("BirthDate", "DateTime(1982, 1, 10)");
p.DynamicQueryExample("Name", "Mike");
Is the code below useful for you?
IEnumerable<DataItem> result = repository.AsQueryable<DataItem>().Where("it[\"Age\"].ToString() == \"30\"");
But for this to work, all your types which can be assigned to the Value member of your Data class needs to have a useful implementation of the ToString method.
Have you tried it[\"Age\"].Equals(object(30))?
Such that:
IEnumerable<DataItem> result =
repository.AsQueryable<DataItem>().Where("it[\"Age\"].Equals(object(30))");
Edit: Updated to correctly cast 30 to object.

Categories

Resources