I am trying to build a LINQ query to query against a large SQL table (7M+ entries) of Documents.
Each document has many DocumentFields :
My goal is to apply successive filters (from 0 to ~10 filters) on the value field of DocumentField:
Here is an example of the filters I want to apply:
[
{fieldId: 32, value: "CET20533"},
{fieldId: 16, value: "882341"},
{fieldId: 12, value: "101746"}
]
What I want is to retrieve every document in my database that matches all of the filters. For the previous example, I want all documents that have a value of CET20533 for the field with the Id "32", the value 882341 for the field with the Id 16, and so on.
I had a first approach :
List<MyFilter> filters = ... // Json deserialization
db.Documents.Where(document =>
filters.All(filter =>
document.DocumentFields.Any(documentField =>
documentField.Id == filter.Id
&& documentField.Value == filter.Value)));
This approach doesn't work : my filters List isn't a primitive type, and therefore cannot be used in a LINQ query.
I had a second approach, which didn't throw an error at me, but only applied 1 filter :
var result = db.Documents.Select(d => d);
foreach (var filter in filters) {
var id = filter.Id;
var value = filter.Value;
result = result.Where(document => document.DocumentFields.Any(documentField =>
documentField.Id == id
&& documentField.Value == value));
}
The problem with this approach is, I believe, some sort of a concurrency problem. I applied a simple pause Thread.Sleep(2000) in each iteration of the foreach to test, and it seems to work.
Questions :
How to remove the pause and still not have concurrency problems ?
Is there a better way to build my query ?
EDIT :
For more clarity, here is an actual example of a document that matches the previous filters example :
You have to Build expression based on your filters and append each in where separately (or not if you can manage it)
db.Documents.Where(ex1).Where(ex2)...
see e.g and MSDN
Or simple case: Start from DocumentFields and retrieve Related Documents. operation Contains works for simple types. that will also simplier in case of building of expression
Quite convinced that your data model is too generic. It will hurt you in terms of program clarity and performance.
But let's go with it for this answer, which I took as a challenge in expression building. The goal is to get a nice queryable that honors the filters on the data server side.
Here's the data model I used, which I think closely matches yours:
public sealed class Document
{
public int Id { get; set; }
// ...
public ICollection<DocumentField> Fields { get; set; }
}
public sealed class DocumentField
{
public int Id { get; set; }
public int DocumentId { get; set; }
public string StringValue { get; set; }
public float? FloatValue { get; set; }
// more typed vales here
}
First, I implement conveniance functions to create predicates for individual fields of individual field types:
public static class DocumentExtensions
{
private static readonly PropertyInfo _piFieldId = (PropertyInfo)((MemberExpression)((Expression<Func<DocumentField, int>>)(f => f.Id)).Body).Member;
private static Expression<Func<DocumentField, bool>> FieldPredicate<T>(int fieldId, T value, Expression<Func<DocumentField, T>> fieldAccessor)
{
var pField = fieldAccessor.Parameters[0];
var xEqualId = Expression.Equal(Expression.Property(pField, _piFieldId), Expression.Constant(fieldId));
var xEqualValue = Expression.Equal(fieldAccessor.Body, Expression.Constant(value, typeof(T)));
return Expression.Lambda<Func<DocumentField, bool>>(Expression.AndAlso(xEqualId, xEqualValue), pField);
}
/// <summary>
/// f => f.<see cref="DocumentField.Id"/> == <paramref name="fieldId"/> && f.<see cref="DocumentField.StringValue"/> == <paramref name="value"/>.
/// </summary>
public static Expression<Func<DocumentField, bool>> FieldPredicate(int fieldId, string value) => FieldPredicate(fieldId, value, f => f.StringValue);
/// <summary>
/// f => f.<see cref="DocumentField.Id"/> == <paramref name="fieldId"/> && f.<see cref="DocumentField.FloatValue"/> == <paramref name="value"/>.
/// </summary>
public static Expression<Func<DocumentField, bool>> FieldPredicate(int fieldId, float? value) => FieldPredicate(fieldId, value, f => f.FloatValue);
// more overloads here
}
Usage:
var fieldPredicates = new[] {
DocumentExtensions.FieldPredicate(32, "CET20533"), // f => f.Id == 32 && f.StringValue == "CET20533"
DocumentExtensions.FieldPredicate(16, "882341"),
DocumentExtensions.FieldPredicate(12, 101746F) // f => f.Id == 12 && f.FloatValue == 101746F
};
Second, I implement an extension method HavingAllFields(also in DocumentExtensions) that creates an IQueryable<Document> where all of the field predicates are satisfied by at least one field:
private static readonly MethodInfo _miAnyWhere = ((MethodCallExpression)((Expression<Func<IEnumerable<DocumentField>, bool>>)(fields => fields.Any(f => false))).Body).Method;
private static readonly Expression<Func<Document, IEnumerable<DocumentField>>> _fieldsAccessor = doc => doc.Fields;
/// <summary>
/// <paramref name="documents"/>.Where(doc => doc.Fields.Any(<paramref name="fieldPredicates"/>[0]) && ... )
/// </summary>
public static IQueryable<Document> HavingAllFields(this IQueryable<Document> documents, IEnumerable<Expression<Func<DocumentField, bool>>> fieldPredicates)
{
using (var e = fieldPredicates.GetEnumerator())
{
if (!e.MoveNext()) return documents;
Expression predicateBody = Expression.Call(_miAnyWhere, _fieldsAccessor.Body, e.Current);
while (e.MoveNext())
predicateBody = Expression.AndAlso(predicateBody, Expression.Call(_miAnyWhere, _fieldsAccessor.Body, e.Current));
var predicate = Expression.Lambda<Func<Document, bool>>(predicateBody, _fieldsAccessor.Parameters);
return documents.Where(predicate);
}
}
Test:
var documents = (new[]
{
new Document
{
Id = 1,
Fields = new[]
{
new DocumentField { Id = 32, StringValue = "CET20533" },
new DocumentField { Id = 16, StringValue = "882341" },
new DocumentField { Id = 12, FloatValue = 101746F },
}
},
new Document
{
Id = 2,
Fields = new[]
{
new DocumentField { Id = 32, StringValue = "Bla" },
new DocumentField { Id = 16, StringValue = "882341" },
new DocumentField { Id = 12, FloatValue = 101746F },
}
}
}).AsQueryable();
var matches = documents.HavingAllFields(fieldPredicates).ToList();
Matches document 1, but not 2.
I usually do something like this: put all your desired Id's for your filter into a list, then use contains.
List<int> myDesiredIds = new List<int> { 1, 2, 3, 4, 5 };
db.documents.Where(x=>myDesiredIds.Contains(x.DocumentId));
Related
I am fetching the products list whose name starts with numbers, but i would like to apply the two more filters like with specific BranchId and isActive.
Query which returns the list of products whose name starts with numbers.
List<string> searchP = new List<string> { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" };
.....Where(x=> searchP.Any(y=> x.ProductName.StartsWith(x.ProductName))).ToList();
I have a list of filters in custom FilterCollection object named filters which has three properties, filtername, operatorname and filtervalue.
filters.Add(FilterKey.BranchId, Filteroperator.Equals, 333); same for isActive, this is what I have passed before one layer back.
I would like to get the products whose name starts with number and having BranchId==value in filter and isActive==filter value i.e true / false.
If I understand your problem correct IsActiv and BranchId are properties of the Items you are searching.
In this case try something like this:
...Where(x => searchP.Any(y=> x.ProductName.StartsWith(x.ProductName))
&& x.IsActive == true/false && x.BranchId == specificVal).ToList();
I would also recommend you check out Regex Patterns instead of your List of startcharacters.
Given something like:
public enum Filteroperator {
Equals
}
public class Filter {
static public Dictionary<Filteroperator, ExpressionType> mapOperator = new() {
{ Filteroperator.Equals, ExpressionType.Equal }
};
public string propertyName;
public Filteroperator op;
public object value;
public Filter(string p, Filteroperator o, object v) {
propertyName = p;
op = o;
value = v;
}
public Func<T, bool> FilterExpression<T>(IEnumerable<T> _) {
// p =>
var parmP = Expression.Parameter(typeof(T), "p");
// p.{propertyName}
var propE = Expression.Property(parmP, propertyName);
var valE = Expression.Constant(value);
var filterBody = Expression.MakeBinary(Filter.mapOperator[op], propE, valE);
var filterExpr = Expression.Lambda<Func<T,bool>>(filterBody, parmP);
return filterExpr.Compile();
}
}
public class FilterCollection : IList<Filter> {
// ...
}
You can build your query with:
var q = data.Where(d => searchP.Any(aDigit => d.ProductName.StartsWith(aDigit)));
foreach (var filter in filters)
q = q.Where(filter.FilterExpression(q));
Consider we have this class :
public class Data
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
public string Field4 { get; set; }
public string Field5 { get; set; }
}
How do I dynamically select for specify columns ? something like this :
var list = new List<Data>();
var result= list.Select("Field1,Field2"); // How ?
Is this the only solution => Dynamic LINQ ?
Selected fields are not known at compile time. They would be specified at runtime
You can do this by dynamically creating the lambda you pass to Select:
Func<Data,Data> CreateNewStatement( string fields )
{
// input parameter "o"
var xParameter = Expression.Parameter( typeof( Data ), "o" );
// new statement "new Data()"
var xNew = Expression.New( typeof( Data ) );
// create initializers
var bindings = fields.Split( ',' ).Select( o => o.Trim() )
.Select( o => {
// property "Field1"
var mi = typeof( Data ).GetProperty( o );
// original value "o.Field1"
var xOriginal = Expression.Property( xParameter, mi );
// set value "Field1 = o.Field1"
return Expression.Bind( mi, xOriginal );
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit( xNew, bindings );
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<Data,Data>>( xInit, xParameter );
// compile to Func<Data, Data>
return lambda.Compile();
}
Then you can use it like this:
var result = list.Select( CreateNewStatement( "Field1, Field2" ) );
In addition for Nicholas Butler and the hint in comment of Matt(that use T for type of input class), I put an improve to Nicholas answer that generate the property of entity dynamically and the function does not need to send field as parameter.
For Use add class as below:
public static class Helpers
{
public static Func<T, T> DynamicSelectGenerator<T>(string Fields = "")
{
string[] EntityFields;
if (Fields == "")
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
else
EntityFields = Fields.Split(',');
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = EntityFields.Select(o => o.Trim())
.Select(o =>
{
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
// compile to Func<Data, Data>
return lambda.Compile();
}
}
The DynamicSelectGenerator method get entity with type T, this method have optional input parameter Fields that if you want to select special field from entity send as a string such as "Field1, Field2" and if you don't send anything to method, it returns all of the fields of entity, you could use this method as below:
using (AppDbContext db = new AppDbContext())
{
//select "Field1, Field2" from entity
var result = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>("Field1, Field2")).ToList();
//select all field from entity
var result1 = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>()).ToList();
}
(Assume that you have a DbContext with name AppDbContext and the context have an entity with name SampleEntity)
You must use reflection to get and set property value with it's name.
var result = new List<Data>();
var data = new Data();
var type = data.GetType();
var fieldName = "Something";
for (var i = 0; i < list.Count; i++)
{
foreach (var property in data.GetType().GetProperties())
{
if (property.Name == fieldName)
{
type.GetProperties().FirstOrDefault(n => n.Name == property.Name).SetValue(data, GetPropValue(list[i], property.Name), null);
result.Add(data);
}
}
}
And here is GetPropValue() method
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
Using Reflection and Expression bulid can do what you say.
Example:
var list = new List<Data>();
//bulid a expression tree to create a paramter
ParameterExpression param = Expression.Parameter(typeof(Data), "d");
//bulid expression tree:data.Field1
Expression selector = Expression.Property(param,typeof(Data).GetProperty("Field1"));
Expression pred = Expression.Lambda(selector, param);
//bulid expression tree:Select(d=>d.Field1)
Expression expr = Expression.Call(typeof(Queryable), "Select",
new Type[] { typeof(Data), typeof(string) },
Expression.Constant(list.AsQueryable()), pred);
//create dynamic query
IQueryable<string> query = list.AsQueryable().Provider.CreateQuery<string>(expr);
var result=query.ToList();
I writing the method in following line for you can work with nested fields taking advantage of Nicholas Butler and Ali.
You can use this method for dynamically creating to lambda for pass to select and also works for nested fields. You can also work with IQueryable cases.
/// <param name="Fields">
/// Format1: "Field1"
/// Format2: "Nested1.Field1"
/// Format3: "Field1:Field1Alias"
/// </param>
public static Expression<Func<T, TSelect>> DynamicSelectGenerator<T, TSelect>(params string[] Fields)
{
string[] EntityFields = Fields;
if (Fields == null || Fields.Length == 0)
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
// input parameter "x"
var xParameter = Expression.Parameter(typeof(T), "x");
// new statement "new Data()"
var xNew = Expression.New(typeof(TSelect));
// create initializers
var bindings = EntityFields
.Select(x =>
{
string[] xFieldAlias = x.Split(":");
string field = xFieldAlias[0];
string[] fieldSplit = field.Split(".");
if (fieldSplit.Length > 1)
{
// original value "x.Nested.Field1"
Expression exp = xParameter;
foreach (string item in fieldSplit)
exp = Expression.PropertyOrField(exp, item);
// property "Field1"
PropertyInfo member2 = null;
if (xFieldAlias.Length > 1)
member2 = typeof(TSelect).GetProperty(xFieldAlias[1]);
else
member2 = typeof(T).GetProperty(fieldSplit[fieldSplit.Length - 1]);
// set value "Field1 = x.Nested.Field1"
var res = Expression.Bind(member2, exp);
return res;
}
// property "Field1"
var mi = typeof(T).GetProperty(field);
PropertyInfo member;
if (xFieldAlias.Length > 1)
member = typeof(TSelect).GetProperty(xFieldAlias[1]);
else member = typeof(TSelect).GetProperty(field);
// original value "x.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = x.Field1"
return Expression.Bind(member, xOriginal);
}
);
// initialization "new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "x => new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var lambda = Expression.Lambda<Func<T, TSelect>>(xInit, xParameter);
return lambda;
}
Usage:
var s = DynamicSelectGenerator<SalesTeam, SalesTeamSelect>(
"Name:SalesTeamName",
"Employee.FullName:SalesTeamExpert"
);
var res = _context.SalesTeam.Select(s);
public class SalesTeam
{
public string Name {get; set; }
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; }
}
public class SalesTeamSelect
{
public string SalesTeamName {get; set; }
public string SalesTeamExpert {get; set; }
}
The OP mentioned Dynamic Linq library, so I'd like to lay out an explanation on its usage.
1. Dynamic Linq Built-In Select
Dynamic Linq has a built-in Select method, which can be used as follows:
var numbers = new List<int> { 1, 2, 3 };
var wrapped = numbers.Select(num => new { Value = num }).ToList();
// the "it" keyword functions as the lambda parameter,
// so essentialy it's like calling: numbers.Select(num => num)
var selectedNumbers = numbers.Select("it");
// the following is the equivalent of calling: wrapped.Select(num => num.Value)
var selectedValues = wrapped.Select("Value");
// the following is the equivalent of calling: numbers.Select(num => new { Value = num })
var selectedObjects = numbers.Select("new(it as Value)");
foreach (int num in selectedNumbers) Console.WriteLine(num);
foreach (int val in selectedValues) Console.WriteLine(val);
foreach (dynamic obj in selectedObjects) Console.WriteLine(obj.Value);
The Downside
There's somewhat a downside using the built-in Select:
Since it's an IQueryable - not IQueryable<T> - extension method, with IQueryable as its return type, common materialization methods - like ToList or FirstOrDefault - can't be used. This is why the above example uses foreach - it's simply the only convenient way of materializing the results.
So to make things more convenient, let's support these methods.
2. Supporting Select<T> in Dynamic Linq (to enable using ToList and alike)
To support Select<T>, it needs to be added into the Dynamic Linq file. The simple steps for doing that are explained in this answer and in my comment on it.
After doing so, it can be used in the following way:
var numbers = new List<int> { 1, 2, 3 };
var wrapped = numbers.Select(num => new { Value = num }).ToList();
// the following is the equivalent of calling: numbers.Select(num => num).ToList()
var selectedNumbers = numbers.Select<int>("it").ToList();
// the following is the equivalent of calling: wrapped.Select(num => num.Value).ToList()
var selectedValues = wrapped.Select<int>("Value").ToList();
// the following is the equivalent of calling: numbers.Select(num => new { Value = num }).ToList()
var selectedObjects = numbers.Select<object>("new(it as Value)").ToList();
The Downside
Arguably, this implementation introduces yet another kind of downside: By having to explicitly parameterize the Select<T> call (e.g., having to call Select<int>), we're losing the dynamic nature of the library.
Nevertheless, since we can now call any materialization Linq method, this usage may still be quite useful.
I simplified the amazing method DynamicSelectGenerator() created by Ali and made this extension method that overrides the LINQ Select() to take a column separated parameters to simplify the usage and for more readability:
public static IEnumerable<T> Select<T>(this IEnumerable<T> source, string parameters)
{
return source.Select(DynamicSelectGenerator<T>(parameters));
}
So instead of:
var query = list.Select(Helpers.DynamicSelectGenerator<Data>("Field1,Field2")).ToList();
Will be:
var query = list.Select("Field1,Field2").ToList();
Another approach I've used is a nested ternary operator:
string col = "Column3";
var query = table.Select(i => col == "Column1" ? i.Column1 :
col == "Column2" ? i.Column2 :
col == "Column3" ? i.Column3 :
col == "Column4" ? i.Column4 :
null);
The ternary operator requires that each field be the same type, so you'll need to call .ToString() on any non-string columns.
I have generate my own class for same purpose of usage.
github gist : https://gist.github.com/mstrYoda/663789375b0df23e2662a53bebaf2c7c
It generates dynamic select lambda for given string and also support for two level nested properties.
Example of usage is :
class Shipment {
// other fields...
public Address Sender;
public Address Recipient;
}
class Address {
public string AddressText;
public string CityName;
public string CityId;
}
// in the service method
var shipmentDtos = _context.Shipments.Where(s => request.ShipmentIdList.Contains(s.Id))
.Select(new SelectLambdaBuilder<Shipment>().CreateNewStatement(request.Fields)) // request.Fields = "Sender.CityName,Sender.CityId"
.ToList();
It compiles the lambda as below:
s => new Shipment {
Sender = new Address {
CityId = s.Sender.CityId,
CityName = s.Sender.CityName
}
}
You can also find my quesion and answer here :c# - Dynamically generate linq select with nested properties
public class SelectLambdaBuilder<T>
{
// as a performence consideration I cached already computed type-properties
private static Dictionary<Type, PropertyInfo[]> _typePropertyInfoMappings = new Dictionary<Type, PropertyInfo[]>();
private readonly Type _typeOfBaseClass = typeof(T);
private Dictionary<string, List<string>> GetFieldMapping(string fields)
{
var selectedFieldsMap = new Dictionary<string, List<string>>();
foreach (var s in fields.Split(','))
{
var nestedFields = s.Split('.').Select(f => f.Trim()).ToArray();
var nestedValue = nestedFields.Length > 1 ? nestedFields[1] : null;
if (selectedFieldsMap.Keys.Any(key => key == nestedFields[0]))
{
selectedFieldsMap[nestedFields[0]].Add(nestedValue);
}
else
{
selectedFieldsMap.Add(nestedFields[0], new List<string> { nestedValue });
}
}
return selectedFieldsMap;
}
public Func<T, T> CreateNewStatement(string fields)
{
ParameterExpression xParameter = Expression.Parameter(_typeOfBaseClass, "s");
NewExpression xNew = Expression.New(_typeOfBaseClass);
var selectFields = GetFieldMapping(fields);
var shpNestedPropertyBindings = new List<MemberAssignment>();
foreach (var keyValuePair in selectFields)
{
PropertyInfo[] propertyInfos;
if (!_typePropertyInfoMappings.TryGetValue(_typeOfBaseClass, out propertyInfos))
{
var properties = _typeOfBaseClass.GetProperties();
propertyInfos = properties;
_typePropertyInfoMappings.Add(_typeOfBaseClass, properties);
}
var propertyType = propertyInfos
.FirstOrDefault(p => p.Name.ToLowerInvariant().Equals(keyValuePair.Key.ToLowerInvariant()))
.PropertyType;
if (propertyType.IsClass)
{
PropertyInfo objClassPropInfo = _typeOfBaseClass.GetProperty(keyValuePair.Key);
MemberExpression objNestedMemberExpression = Expression.Property(xParameter, objClassPropInfo);
NewExpression innerObjNew = Expression.New(propertyType);
var nestedBindings = keyValuePair.Value.Select(v =>
{
PropertyInfo nestedObjPropInfo = propertyType.GetProperty(v);
MemberExpression nestedOrigin2 = Expression.Property(objNestedMemberExpression, nestedObjPropInfo);
var binding2 = Expression.Bind(nestedObjPropInfo, nestedOrigin2);
return binding2;
});
MemberInitExpression nestedInit = Expression.MemberInit(innerObjNew, nestedBindings);
shpNestedPropertyBindings.Add(Expression.Bind(objClassPropInfo, nestedInit));
}
else
{
Expression mbr = xParameter;
mbr = Expression.PropertyOrField(mbr, keyValuePair.Key);
PropertyInfo mi = _typeOfBaseClass.GetProperty( ((MemberExpression)mbr).Member.Name );
var xOriginal = Expression.Property(xParameter, mi);
shpNestedPropertyBindings.Add(Expression.Bind(mi, xOriginal));
}
}
var xInit = Expression.MemberInit(xNew, shpNestedPropertyBindings);
var lambda = Expression.Lambda<Func<T,T>>( xInit, xParameter );
return lambda.Compile();
}
Thank you #morio. Your comment about Expression<Func<T, T>> is exactly what I needed to make this work.
I do not know how to perform an anonymous projection which seems like what most want. I say I want Field1 and Field2 from Data and I get back something like: new { Field1 = o.Field1, Field2 = o.Field2 };
But I have a need similar to many where I want to plot x and y values, but don't know until run time which ones they are.
So rather than use an anonymous object, I create one that has the properties I want. In this case, X and Y.
Here are the source and target classes:
public class Source
{
public int PropertyA { get; set; }
public double PropertyB { get; set; }
public double PropertyC { get; set; }
}
public class Target
{
public double X { get; set; }
public double Y { get; set; }
}
And here is the code that does the mapping between the Source and the Target.
public static class SelectBuilder
{
/// <summary>
/// Creates a Func that can be used in a Linq Select statement that will map from the source items to a new target type.
/// Typical usage pattern is that you have an Entity that has many properties, but you want to dynamically set properties
/// on a smaller target type, AND, you don't know the mapping at compile time.
/// For example, you have an Entity that has a year and 10 properties. You want to have time (year) as the X axis, but
/// the user can chose any of the 10 properties to plot on the y axis. This would allow you to map one of the entity
/// properties to the Y value dynamically.
/// </summary>
/// <typeparam name="TSource">Type of the source, for example, and Entity Framework entity.</typeparam>
/// <typeparam name="TTarget">Type of the target, a projection of a smaller number of properties than the entity has.</typeparam>
/// <param name="propertyMappings">A list of named tuples that map the sourceProperty to the targetProperty.</param>
/// <returns>A func that can be used inside the Select.
/// So if
/// var select = SelectBuilder.GetSelectStatement<Source, Target>(propertyMappings), then
/// you can perform the select,
/// var results = items.Select(select);</returns>
public static Expression<Func<TSource, TTarget>> GetSelectStatement<TSource, TTarget>(IEnumerable<(string sourceProperty, string targetProperty)> propertyMappings)
{
// Get the source parameter, "source". This will allow the statement to be "X = source.SourceA".
// It needs to be of the source type, and the name is what will be used in the Select lambda.
var sourceParameter = Expression.Parameter(typeof(TSource), "source");
// Now define the ability to create a new Target type.
var newTarget = Expression.New(typeof(TTarget));
// Now develop the bindings or member assignments for each property.
var bindings = new List<MemberAssignment>();
foreach (var propertyMapping in propertyMappings)
{
var sourceMemberInfo = typeof(TSource).GetProperty(propertyMapping.sourceProperty);
var targetMemberInfo = typeof(TTarget).GetProperty(propertyMapping.targetProperty);
// This allows getting the value. Source parameter will provide the "source" part and sourceMemberInfo the property name.
// For example, "source.SourceA".
var sourceValue = Expression.Property(sourceParameter, sourceMemberInfo);
// Provide conversion in the event there is not a perfect match for the type.
// For example, if SourceA is int and the target X is double?, we need to convert from int to double?
var convertExpression = Expression.Convert(sourceValue, targetMemberInfo.PropertyType);
// Put together the target assignment, "X = Convert(source.SourcA, double?)" (TODO: How does the convert actually happen?)
var targetAssignment = Expression.Bind(targetMemberInfo, convertExpression);
bindings.Add(targetAssignment);
}
var memberInit = Expression.MemberInit(newTarget, bindings);
// Here if we map SourceA to X and SourceB to Y the lambda will be:
// {source => new Target() {X = Convert(source.SourceA, Nullable`1), Y = Convert(source.SourceB, Nullable`1)}}
var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInit, sourceParameter);
return lambda;//.Compile();
}
}
And finally a unit test that works.
[Fact(DisplayName = "GetSelectStatement works")]
public void Test2()
{
// Arrange
var source = new Source { PropertyA = 1, PropertyB = 2, PropertyC = 3 };
var expectedX = Convert.ToDouble(source.PropertyA);
var expectedY = Convert.ToDouble(source.PropertyB);
var items = new List<Source> { source }.AsQueryable();
// Let's map SourceA to X and SourceB to Y.
var propertyMappings = new List<(string sourceProperty, string targetProperty)>
{
("PropertyA", "X"), ("PropertyB", "Y")
//(nameof(Source.PropertyA), nameof(Target.X)),
//(nameof(Source.PropertyB), nameof(Target.Y))
};
// Act
var select = SelectBuilder.GetSelectStatement<Source, Target>(propertyMappings);
var actual = items.Select(select).First();
// Assert
actual.X.Should().Be(expectedX);
actual.Y.Should().Be(expectedY);
}
I've edited my previous answer since now I know how to convert from int to double. I've also made the unit test easier to understand.
I hope this helps others.
Using ExpandoObject you can build a dynamic objects or return the full object from the example below.
public object CreateShappedObject(object obj, List<string> lstFields)
{
if (!lstFields.Any())
{
return obj;
}
else
{
ExpandoObject objectToReturn = new ExpandoObject();
foreach (var field in lstFields)
{
var fieldValue = obj.GetType()
.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
.GetValue(obj, null);
((IDictionary<string, object>)objectToReturn).Add(field, fieldValue);
}
return objectToReturn;
}
}
The following is an example of how to use this from your controller.
http://localhost:12345/api/yourapi?fields=field1,field2
public IHttpActionResult Get(string fields = null)
{
try
{
List<string> lstFields = new List<string>();
if (fields != null)
{
lstFields = fields.ToLower().Split(',').ToList();
}
// Custom query
var result = db.data.Select(i => CreateShappedObject(new Data()
, lstFields)).ToList();
return Ok(result);
}
catch(Exception)
{
return InternalServerError();
}
}
var result = from g in list.AsEnumerable()
select new {F1 = g.Field1,F2 = g.Field2};
I'm trying to construct an expression that ultimately results in a query like
SELECT p.*
FROM MyEntity p
WHERE EXISTS(SELECT *
FROM filters
WHERE (filter.type = 1
AND filter.objectid = p.id
AND filter.value = 1
OR filter.type = 1
AND filter.objectid = p.id
AND filter.value = 2))
AND EXISTS(...)
Obviously it won't look exactly like that, but that's the general idea.
I'm using PredicateBuilder to build the query based on the filters passed in, so I have something like this:
var query = context.Set<MyEntity>().AsExpandable();
var predicate = PredicateBuilder.New<MyEntity>(true);
//loop through the group filters. The filters in a group have an or relationship
foreach (FilterGroup group in filters)
{
predicate = predicate.And(
p => context.Set<FilteringValue>().AsExpandable().Any(getFilteringPredicate(p,group ))
);
}
return query.Where(predicate);
And the getFilteringPredicateMethod:
Expression<Func<FilteringValue,bool>> getFilteringPredicate(MyEntity p, FilterGroup filters) {
var fPredicate = PredicateBuilder.New<FilteringValue>(true);
foreach(var filter in filters.FilterList)
{
fPredicate= fPredicate.Or(fv => fv.objectid == p.Id && fv.Type== 1 && fv.value == filter.Value);
}
return fPredicate
}
This seems relatively simple, however I'm getting the error
variable 'p' of type 'Models.MyEntity' referenced from scope '', but it is not defined.
Is there no way to pass the product object into the getFilteringPredicate() method? MyEntity and Filter are not related in Entity Framework.
So... I think I finally got it, you want to relate two expression parameters and build up a composite query (what I mean by the informal definition of "composite" is a subquery having a reference to the main query parameter(s)):
Unfortunately, LinqKit does not support multi-parameter expressions 'AFAIK', which is something that would be a perfect match for your case:
Well, anyway... here it goes. By the way FilteringValues and MyEntities are just two DbSets, I just happen to be using LinqPad to test this out ATM (Questions?):
void Main(string[] args)
{
var entityQuery = MyEntities.AsExpandable();
var filterGroups = GetFilterGroups();
// Initialize with TRUE since no group filter implies Everything matches
var predicate = PredicateBuilder.New<MyEntity>(true);
var filteringValueQuery = FilteringValues.AsExpandable();
foreach (var g in filterGroups)
{
if (!g.FilterList.Any())
{
// If we have no filters in the group, skip
continue;
}
var expressionForGroupFilters = BuildExpressionForGroupFilters(g.FilterList);
predicate = predicate.And(entity => filteringValueQuery.Any(filteringValue => expressionForGroupFilters.Invoke(entity, filteringValue)));
}
entityQuery = entityQuery.Where(predicate);
var data = entityQuery.ToList();
data.Dump();
}
public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForSingleFilter(Filter groupFilter)
{
var value = groupFilter.Value;
return (entity, filteringValue) =>
filteringValue.Type == 1
&& filteringValue.ObjectId == entity.Id
&& filteringValue.Value == value;
}
public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForGroupFilters(IReadOnlyCollection<Filter> groupFilters)
{
Expression<Func<MyEntity, FilteringValue, bool>> result = null;
foreach (var groupFilter in groupFilters)
{
var expression = BuildExpressionForSingleFilter(groupFilter);
if (result == null)
{
result = expression;
continue;
}
var tempResult = result.Expand();
result = (entity, filteringValue) => tempResult.Invoke(entity, filteringValue) || expression.Invoke(entity, filteringValue);
}
return result.Expand();
}
public static FilterGroup CreateFilterGroupWithValues(params int[] values)
{
var filterList = values
.Select(x => new Filter { Value = x })
.ToList();
return new FilterGroup { FilterList = filterList };
}
public static IEnumerable<FilterGroup> GetFilterGroups()
{
return new[] {CreateFilterGroupWithValues(0, 2, 4), CreateFilterGroupWithValues(1)};
}
public class Filter
{
public int Value { get; set; }
}
public class FilterGroup
{
public FilterGroup()
{
FilterList = new List<Filter>();
}
public List<Filter> FilterList { get; set; }
}
I am working on an ASP.NET MVC projet using EF code first, and I am facing a situation where I need to order by an enum description:
public partial class Item
{
public enum MyEnumE
{
[Description("description of enum1")]
Enum1,
[Description("description of enum2")]
Enum2,
...
}
public MyEnumE MyEnum { get; set; }
}
Here is the Search and SortAndPaginate functions:
public async Task<IPagedList<Item>> Search(ItemCriteria criteria, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb)
{
var itemFilter = GenerateFilter(criteria);
var items = entities.Items.Where(itemFilter);
return await SortAndPaginate(items, sortName, sortOrder, pageNb);
}
private async Task<IPagedList<Item>> SortAndPaginate(IQueryable<Item> items, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb)
{
IOrderedQueryable<Item> result = null;
switch (sortName)
{
...
case SortableTypeE.Type:
result = sortOrder == SortOrder.TypeE.ASC
? items.OrderBy(i => i.MyEnum.GetDescription())
: items.OrderByDescending(i => i.MyEnum.GetDescription());
result = result.ThenBy(i => i.SomeOtherProperty);
break;
...
}
if (result != null)
{
return await result.ToPagedListAsync(pageNb, 10);
}
return PagedListHelper.Empty<Item>();
}
The problem is that the Item table can be quite huge.
I thought about calling ToListAsync right after entities.Items.Where(itemFilter) but this will get back all filtered items although I only need one page. Does not sound like a good idea.
But if I don't do that EF won't know about GetDescription() mathod and I can only think about two solutions:
- Change my database column to a string (the enum description) instead of the enum itself (but sounds like a hack to me)
- Or alphabetically order MyEnumE components directly in the enum declaration (seems dirty and quite unmaintainable too)
I'm quite stuck since I'm concerned about performances if I call ToListAsync right after filtering, all other solutions seem dirty, and I absolutely need a IPagedList returned from the Search method.
Would anyone have an idea about how to deal with this issue ?
Thanks a lot.
UPDATE
Here is the GetDescription method (can change it if necessary):
public static string GetDescription(this Enum e)
{
FieldInfo fi = e.GetType().GetField(e.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
return attributes[0].Description;
else
return e.ToString();
}
SOLUTIONS
I'll finally go for Ivan Stoev's suggestion because my project is mainly based on Linq (using Linq instead of stored procedures etc.), so this solution seems more suitable to my particular case than creating reference tables.
However Niyoko Yuliawan's and Michael Freidgeim's are also really good answers to me, anyone reading this post and having a more database approach should go for their solutions ;)
Thanks a lot to all of you.
I would go with dynamic expression. It's more flexible and can easily be changed w/o affecting the database tables and queries.
However, instead of sorting by description strings in the database, I would create ordered map in memory, associating int "order" value with each enum value like this:
public static class EnumHelper
{
public static Expression<Func<TSource, int>> DescriptionOrder<TSource, TEnum>(this Expression<Func<TSource, TEnum>> source)
where TEnum : struct
{
var enumType = typeof(TEnum);
if (!enumType.IsEnum) throw new InvalidOperationException();
var body = ((TEnum[])Enum.GetValues(enumType))
.OrderBy(value => value.GetDescription())
.Select((value, ordinal) => new { value, ordinal })
.Reverse()
.Aggregate((Expression)null, (next, item) => next == null ? (Expression)
Expression.Constant(item.ordinal) :
Expression.Condition(
Expression.Equal(source.Body, Expression.Constant(item.value)),
Expression.Constant(item.ordinal),
next));
return Expression.Lambda<Func<TSource, int>>(body, source.Parameters[0]);
}
public static string GetDescription<TEnum>(this TEnum value)
where TEnum : struct
{
var enumType = typeof(TEnum);
if (!enumType.IsEnum) throw new InvalidOperationException();
var name = Enum.GetName(enumType, value);
var field = typeof(TEnum).GetField(name, BindingFlags.Static | BindingFlags.Public);
return field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? name;
}
}
The usage would be like this:
case SortableTypeE.Type:
var order = EnumHelper.DescriptionOrder((Item x) => x.MyEnum);
result = sortOrder == SortOrder.TypeE.ASC
? items.OrderBy(order)
: items.OrderByDescending(order);
result = result.ThenBy(i => i.SomeOtherProperty);
break;
which would generate expression like this:
x => x.MyEnum == Enum[0] ? 0 :
x.MyEnum == Enum[1] ? 1 :
...
x.MyEnum == Enum[N-2] ? N - 2 :
N - 1;
where 0,1,..N-2 is the corresponding index in the value list sorted by description.
Alternative 1
You can do it by projecting enum into custom value and sort by it.
Example:
items
.Select(x=> new
{
x,
Desc = (
x.Enum == Enum.One ? "Desc One"
: x.Enum == Enum.Two ? "Desc Two"
... and so on)
})
.OrderBy(x=>x.Desc)
.Select(x=>x.x);
Entity framework then will generate SQL something like this
SELECT
*
FROM
YourTable
ORDER BY
CASE WHEN Enum = 1 THEN 'Desc One'
WHEN Enum = 2 THEN 'Desc Two'
...and so on
END
If you have a lot of query like this, you can create extension method
public static IQueryable<Entity> OrderByDesc(this IQueryable<Entity> source)
{
return source.Select(x=> new
{
x,
Desc = (
x.Enum == Enum.One ? "Desc One"
: x.Enum == Enum.Two ? "Desc Two"
... and so on)
})
.OrderBy(x=>x.Desc)
.Select(x=>x.x);
}
And call it when you need it
var orderedItems = items.OrderByDesc();
Alternative 2
Another alternative solution is to create additional table that map enum value to enum description and join your table to this table. This solution will be more performant because you can create index on enum description column.
Alternative 3
If you want dynamic expression based on your enum description attribute, you can build yourself
Helper Class
public class Helper
{
public MyEntity Entity { get; set; }
public string Description { get; set; }
}
Get dynamically built expression
public static string GetDesc(MyEnum e)
{
var type = typeof(MyEnum);
var memInfo = type.GetMember(e.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute),
false);
return ((DescriptionAttribute)attributes[0]).Description;
}
private static Expression<Func<MyEntity, Helper>> GetExpr()
{
var descMap = Enum.GetValues(typeof(MyEnum))
.Cast<MyEnum>()
.ToDictionary(value => value, GetDesc);
var paramExpr = Expression.Parameter(typeof(MyEntity), "x");
var expr = (Expression) Expression.Constant(string.Empty);
foreach (var desc in descMap)
{
// Change string "Enum" below with your enum property name in entity
var prop = Expression.Property(paramExpr, typeof(MyEntity).GetProperty("Enum"));
expr = Expression.Condition(Expression.Equal(prop, Expression.Constant(desc.Key)),
Expression.Constant(desc.Value), expr);
}
var newExpr = Expression.New(typeof(Helper));
var bindings = new MemberBinding[]
{
Expression.Bind(typeof(Helper).GetProperty("Entity"), paramExpr),
Expression.Bind(typeof(Helper).GetProperty("Description"), expr)
};
var body = Expression.MemberInit(newExpr, bindings);
return (Expression<Func<MyEntity, Helper>>) Expression.Lambda(body, paramExpr);
}
Call it like this
var e = GetExpr();
items.Select(e)
.OrderBy(x => x.Description)
.Select(x => x.Entity);
Change my database column to a string (the enum description) instead
of the enum itself (but sounds like a hack to me).
Opposite, for data-driven application it's better to describe Item property in the database reference table MyItemProperty(MyPropKey,MyPropDescription) and have MyPropKey column in your Items table.
It has a few benefits, e.g.
allow to add new property values without need to change code;
allow to write SQL reports having all information in the database without writing c#;
performance optimisation can be done on SQL
level just by requesting one page;
no enum - less code to maintain.
To keep it simple and with a good performance, I would order the enum manually, you only have to do it once, and it will help a lot
public enum MyEnumE
{
Enum1 = 3,
Enum2 = 1,
Enum3 = 2, // set the order here...
}
Here's a simplified example using a join:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace ConsoleApplication
{
public partial class Item
{
public enum MyEnumE
{
[Description("description of enum1")]
Enum1,
[Description("description of enum2")]
Enum2
}
public Item(MyEnumE myEnum)
{
MyEnum = myEnum;
}
public MyEnumE MyEnum { get; set; }
}
class Program
{
private static IEnumerable<KeyValuePair<int, int>> GetEnumRanks(Type enumType)
{
var values = Enum.GetValues(enumType);
var results = new List<KeyValuePair<int, string>>(values.Length);
foreach (int value in values)
{
FieldInfo fieldInfo = enumType.GetField(Enum.GetName(enumType, value));
var attribute = (DescriptionAttribute)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute));
results.Add(new KeyValuePair<int, string>(value, attribute.Description));
}
return results.OrderBy(x => x.Value).Select((x, i) => new KeyValuePair<int, int>(x.Key, i));
}
static void Main(string[] args)
{
var itemsList = new List<Item>();
itemsList.Add(new Item(Item.MyEnumE.Enum1));
itemsList.Add(new Item(Item.MyEnumE.Enum2));
itemsList.Add(new Item(Item.MyEnumE.Enum2));
itemsList.Add(new Item(Item.MyEnumE.Enum1));
IQueryable<Item> items = itemsList.AsQueryable();
var descriptions = GetEnumRanks(typeof(Item.MyEnumE));
//foreach (var i in descriptions)
// Console.WriteLine(i.Value);
var results = items.Join(descriptions, a => (int)a.MyEnum, b => b.Key, (x, y) => new { Item = x, Rank = y.Value }).OrderBy(x => x.Rank).Select(x => x.Item);
foreach (var i in results)
Console.WriteLine(i.MyEnum.ToString());
Console.WriteLine("\nPress any key...");
Console.ReadKey();
}
}
}
I had a similar problem to solve, only that my ordering had to be dynamic, that is the sort by column parameter is a string.
The boolean sorting also had to be customized in that sense that true comes before false (e.g. 'Active' is before 'Inactive').
I'm sharing here the complete code with you, so you can spare your time. In case you find spots for improvement, please feel free to share in a comment.
private static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> query, SortField sortField)
{
var queryParameterExpression = Expression.Parameter(typeof(T), "x");
var orderByPropertyExpression = GetPropertyExpression(sortField.FieldName, queryParameterExpression);
Type orderByPropertyType = orderByPropertyExpression.Type;
LambdaExpression lambdaExpression = Expression.Lambda(orderByPropertyExpression, queryParameterExpression);
if (orderByPropertyType.IsEnum)
{
orderByPropertyType = typeof(int);
lambdaExpression = GetExpressionForEnumOrdering<T>(lambdaExpression);
}
else if (orderByPropertyType == typeof(bool))
{
orderByPropertyType = typeof(string);
lambdaExpression =
GetExpressionForBoolOrdering(orderByPropertyExpression, queryParameterExpression);
}
var orderByExpression = Expression.Call(
typeof(Queryable),
sortField.SortDirection == SortDirection.Asc ? "OrderBy" : "OrderByDescending",
new Type[] { typeof(T), orderByPropertyType },
query.Expression,
Expression.Quote(lambdaExpression));
return query.Provider.CreateQuery<T>(orderByExpression);
}
The shared GetPropertyExpression has been simplified a bit, to exclude the nested property handling.
private static MemberExpression GetPropertyExpression(string propertyName, ParameterExpression queryParameterExpression)
{
MemberExpression result = Expression.Property(queryParameterExpression, propertyName);
return result;
}
Here is the slightly modified code (from the accepted solution) to handle the Enum ordering.
private static Expression<Func<TSource, int>> GetExpressionForEnumOrdering<TSource>(LambdaExpression source)
{
var enumType = source.Body.Type;
if (!enumType.IsEnum)
throw new InvalidOperationException();
var body = ((int[])Enum.GetValues(enumType))
.OrderBy(value => GetEnumDescription(value, enumType))
.Select((value, ordinal) => new { value, ordinal })
.Reverse()
.Aggregate((Expression)null, (next, item) => next == null ? (Expression)
Expression.Constant(item.ordinal) :
Expression.Condition(
Expression.Equal(source.Body, Expression.Convert(Expression.Constant(item.value), enumType)),
Expression.Constant(item.ordinal),
next));
return Expression.Lambda<Func<TSource, int>>(body, source.Parameters[0]);
}
And the boolean ordering as well.
private static LambdaExpression GetExpressionForBoolOrdering(MemberExpression orderByPropertyExpression, ParameterExpression queryParameterExpression)
{
var firstWhenActiveExpression = Expression.Condition(orderByPropertyExpression,
Expression.Constant("A"),
Expression.Constant("Z"));
return Expression.Lambda(firstWhenActiveExpression, new[] { queryParameterExpression });
}
Also the GetEnumDescription has been modified to receive the Type as the parameter, so it can be called without a generic.
private static string GetEnumDescription(int value, Type enumType)
{
if (!enumType.IsEnum)
throw new InvalidOperationException();
var name = Enum.GetName(enumType, value);
var field = enumType.GetField(name, BindingFlags.Static | BindingFlags.Public);
return field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? name;
}
The SortField is a simple abstraction containing the string column property to be sorted upon and the direction of the sort. For the sake of simplicity I am also not sharing that one here.
Cheers!
Firstly I have seen IEqualityComparer for anonymous type and the answers there do not answer my question, for the obvious reason that I need an IEqualityComparer not and IComparer for use with Linq's Distinct() method. I have checked the other answers too and these fall short of a solution...
The Problem
I have some code to manipulate and pull records in from a DataTable
var glext = m_dtGLExt.AsEnumerable();
var cflist =
(from c in glext
orderby c.Field<string>(m_strpcCCType),
c.Field<string>(m_strpcCC),
c.Field<string>(m_strpcCCDesc),
c.Field<string>(m_strpcCostItem)
select new
{
CCType = c.Field<string>(m_strpcCCType),
CC = c.Field<string>(m_strpcCC),
CCDesc = c.Field<string>(m_strpcCCDesc),
CostItem = c.Field<string>(m_strpcCostItem)
}).Distinct();
but I need the distinct method to be case insensitive. What is throwing me here is the use of anonymous types.
Attempted Solution 1
If I had SomeClass which had concrete objects I could obviously do
public class SumObject
{
public string CCType { get; set; }
public string CC { get; set; }
public string CCDesc { get; set; }
public string CostItem { get; set; }
}
I could obviously do this
List<SumObject> lso = new List<SumObject>()
{
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Rooney", CostItem = "I477" },
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Zidane", CostItem = "I677" },
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Falcao", CostItem = "I470" },
};
var e = lso.Distinct(new SumObjectComparer()); // Great :]
where
class SumObjectComparer : IEqualityComparer<SumObject>
{
public bool Equals(SumObject x, SumObject y)
{
if (Object.ReferenceEquals(x, y))
return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.CCType.CompareNoCase(y.CCType) == 0 &&
x.CC.CompareNoCase(y.CC) == 0 &&
x.CCDesc.CompareNoCase(y.CCDesc) == 0 &&
x.CostItem.CompareNoCase(y.CostItem) == 0;
}
public int GetHashCode(SumObject o)
{
if (Object.ReferenceEquals(o, null))
return 0;
int hashCCType = String.IsNullOrEmpty(o.CCType) ?
0 : o.CCType.ToLower().GetHashCode();
int hashCC = String.IsNullOrEmpty(o.CC) ?
0 : o.CC.ToLower().GetHashCode();
int hashCCDesc = String.IsNullOrEmpty(o.CCDesc) ?
0 : o.CCDesc.ToLower().GetHashCode();
int hashCostItem = String.IsNullOrEmpty(o.CostItem) ?
0 : o.CostItem.ToLower().GetHashCode();
return hashCCType ^ hashCC ^ hashCCDesc ^ hashCostItem;
}
}
However, the use of anonymous types in the above Linq query are throwing me.
Attempted Solution 2
To attempt another solution to this (and because I have the same issue elsewhere) I generated the following generic comparer class
public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
Func<T, T, bool> compareFunction;
Func<T, int> hashFunction;
public GenericEqualityComparer(Func<T, T, bool> compareFunction, Func<T, int> hashFunction)
{
this.compareFunction = compareFunction;
this.hashFunction = hashFunction;
}
public bool Equals(T x, T y) { return compareFunction(x, y); }
public int GetHashCode(T obj) { return hashFunction(obj); }
}
so that I could attempt to do
var comparer = new GenericEqualityComparer<dynamic>(
(x, y) => { /* My equality stuff */ },
o => { /* My hash stuff */ });
but this casts the returned value as IEnumerable<dynamic> which in turn effects my forthcoming use of cflist, so that in a following query the join fails.
var cf =
(from o in cflist
join od in glext
on new { o.CCType, o.CC, o.CCDesc, o.CostItem } equals new
{
CCType = od.Field<string>(m_strpcCCType),
CC = od.Field<string>(m_strpcCC),
CCDesc = od.Field<string>(m_strpcCCDesc),
CostItem = od.Field<string>(m_strpcCostItem)
}
into c
select new { ... }
I don't want to get into ugly casting to and from IEnumerable<T>s due to the heavy use of this code...
Question
Is there a way I can create my an IEquailityComparer for my anonymous types?
Thanks for your time.
Is there a way I can create my an IEquailityComparer for my anonymous types?
Sure. You just need to use type inference. For example, you could have something like:
public static class InferredEqualityComparer
{
public static IEqualityComparer<T> Create<T>(
IEnumerable<T> example,
Func<T, T, bool> equalityCheck,
Func<T, int> hashCodeProvider)
{
return new EqualityComparerImpl<T>(equalityCheck, hashCodeProvider);
}
private sealed class EqualityComparerImpl<T> : IEqualityComparer<T>
{
// Implement in the obvious way, remembering the delegates and
// calling them appropriately.
}
}
Then:
var glext = m_dtGLExt.AsEnumerable();
var query = from c in glext
orderby ...
select new { ... };
var comparer = InferredEqualityComparer.Create(query,
(x, y) => { ... },
o => { ... }
);
var distinct = query.Distinct(comparer);
Basically the first parameter to the method is just used for type inference, so that the compiler can work out what type to use for the lambda expression parameters.
You could create the comparer ahead of time by creating a sample of the anonymous type:
var sample = new[] { new { ... } };
var comparer = InferredExqualityComparer.Create(sample, ...);
var distinct = (... query here ... ).Distinct(comparer);
but then any time you change the query you've got to change the sample too.
This post may get what you want. Although for .NET 2.0 it also works for newer versions (see the bottom of this post for how to achieve this). In contrast to Jon Skeets solution we won´t use a factory-method like create. But this is only syntactic sugar I think.