Introduction
Last week I was playing with LINQ and it was fun till I tried to dynamically generate a query. It can be done, but not in the way I would like it to be.
Background
I made a Car
class with two properties, Name
and Year
, and want to be able to filter my Cars List
depending
on the selected column in my grid which displays a List
(by Name
, or by Year
).
While I tried to do this, I didn't like the options I found on Google (Reflection, Expressions, predicates, Dynamic LINQ library...) and thought it could be easier ...
The main problem for me seems to be that the property on which I do filtering must be set while I write my code, so I can't change the property till I change my code,
or write several queries and do some on them....not good.
Then I found out that the only way (I know of) to get the name of my property is over
Reflection...not nice again.
So I thought why not make custom properties, properties that know their name as
car.Name.PropertyName
.
So I wrote this little struct:
public struct Property<T>
{
#region fields
private string _name;
private T _value;
#endregion
#region Properties
public string PropertyName
{get { return _name; }set { _name = value; }}
public dynamic PropertyValue
{get { return _value; }set { _value = value; }
}
#endregion
}
And my Car
class would be like this:
public class Car
{
private Property<string> _name;
public Property<string> Name
{
get {_name.PropertyName = "Name";
return _name;
}
set {
_name = value;
}
}
private Property<int> _year;
public Property<int> Year
{
get {_year.PropertyName = "Year";
return _year;
}
set { _year = value;
}
}
}
This was OK, but the problem occurred in my buton_click
on:
Car c1 = new Car(); c1.Name = "Audi";
Because c1.Name
isn't a string, I add to my custom property struct
an implicit operator overload like this:
public static implicit operator Property<T>(T temp )
{
var local = new Property<T> {PropertyValue = temp};
return local;
}
But when I do this, I lose my original instance of Car
because in this method, as you can see,
I create a new Property<T>
, and this new Property<T>
doesn't have the PropertyName
set.
That's why I have in my Car
class, on every property, propertyName = "TheNameOfProperty" and then return
the value.
The error was gone, but I must also be able to write something like this:
string carName = c1.Name
Also, c1.Name return Property<T>
is not a string.
So I wrote a new implicit operator overload in my custom Property struct.
public static implicit operator T(Property<T> tt)
{
return tt.PropertyValue;
}
Now everything works great.
But to use Custom Properties, I must be able to iterate through my collection of properties in my instance of Car
(Name
, Year
....etc)
I will add a List
in my Car
class which will contain all the properties of the instance. I read somewhere that a HashSet
collection is fast so
I will use it for this purpose.
The best place for adding a property to my list is in the setter of the property after (_name=value). But there's a trick thing. When I set the value of my property:
car1.Name = "Audi"
The implicit operator Property<T>
occurs and a new instance of Property
is returned, so in the list of properties, we need to override
the Add
method so that it deletes the old instance, in this case,
Name
property, and replaces it with a new one.
public class ListOfProperties : System.Collections.Generic.HashSet<IProperty>
{
public new bool Add(IProperty p)
{
int nesto = this.RemoveWhere(x => x.PropertyName.Equals(p.PropertyName));
if (base.Add(p))
return true;
else
return false;
}
}
As you can see, we create two interfaces to make things easier, IProperty
and IProperties
.
public interface IProperty
{
string PropertyName { get; set; }
dynamic PropertyValue { get; set; }
}
public interface IProperties
{
ListOfProperties Properties { get; }
}
So my custom property struct now looks like:
public struct Property<T> : IProperty
{
#region fields
private string _name;
private T _value;
#endregion
#region Properties
public string PropertyName
{
get { return _name; }
set { _name = value; }
}
public dynamic PropertyValue
{
get { return _value; }
set { _value = value; }
}
#endregion
#region Operator Overloading
public static implicit operator Property<T>(T temp )
{
var local = new Property<T> {PropertyValue = temp};
return local;
}
public static implicit operator T(Property<T> tt)
{
return tt.PropertyValue;
}
#endregion
}
Using the code
My Car
class now looks like this:
public class Car:IProperties
{
#region Implementation of IProperties
ListOfProperties list = new ListOfProperties();
public ListOfProperties Properties
{
get { return list; }
}
#endregion
private Property<string> _name;
public Property<string> Name
{
get { _name.PropertyName = "Name";
return _name;
}
set {
_name = value;
Properties.Add(Name);
}
}
private Property<int> _year;
public Property<int> Year
{
get { _year.PropertyName = "Year";
return _year;
}
set { _year = value;
Properties.Add(Year);
}
}
}
And the template for those who have Resharper (others can skip this):
private Property<$type$> $field$;public Property<$type$> $property$
{
get {
$field$.PropertyName = "$property$";
return $field$;
}
set {
$field$ = value;
Properties.Add($property$);
}
}
And finally, using our code in a form on button_click
(note that "Name
" can be variable, and the user must know
the type of PropertyValue
(for example, you must pass string value for "Name", in this case "Audi"; if you pass for example
an integer, an error will occur since the type of propertyValue
is dynamic
).
var cars = new List<Car>();
var car1 = new Car(){Name="Audi",Year=2010};
var car2 = new Car() {Name = "Mercedes", Year = 2012};
cars.Add(car1);
cars.Add(car2);
var audi = (from c in cars
let carProperties = c.Properties
from carProperty in carProperties
where carProperty.PropertyName == "Name"
&& carProperty.PropertyValue == "Audi"
select c).FirstOrDefault();
And we can also do this:
List<IProperties> AllKindsOfObjects = new List<IProperties>();
var car1 = new Car(){Name="Audi",Year=2010};
var car2 = new Car() {Name = "Mercedes", Year = 2012};
AllKindsOfObjects.Add(car1);
AllKindsOfObjects.Add(car2);
var racun1 = new Racuni() {Ime = "Elvis", Prezime = "Begluk"};
AllKindsOfObjects.Add(racun1);
string temp = "Name";
var ListOfObjectsThatHavePropertyCalledName = from c in AllKindsOfObjects
let objectProperties = c.Properties
from objectProperty in objectProperties
where objectProperty.PropertyName == temp
select c;
In the sample above, ListOfObjectsThatHavePropertyCalledName
will have two objects,
car1
and car2
.
Testing speed
To test the query speed of my dynamically generated LINQ query with custom properties,
I used a class with five custom properties and generated a List
of 1,000,000 records in it.
The initialization of objects and adding objects to the list took 6 seconds. And the query itself took only 70
milliseconds to find one object of many.
My PC have 4 GB of DDR2 and a 3.2 Ghz CoreDuo CPU.
Issues
I think some things can be made better, for example it would be a lot nicer if
I did not need to add properties n-times to my Properties
collections,
and if I could use T
instead of dynamic
and it really bugs me that
I have to set PropertyName
every time. But for now, this is it, and if
I figure out a better
and simpler way to do this, I will let you know :)