Trying to convert a sequence of int to delimited string - asp.net-mvc-3

I have been using a DelimitedStringHelper extension to convert a sequence of items to a delimited string with IEnumerable<T>. By default, ToString() is called on each item in the sequence to formulate the result using a default delimiter of ', '.
This had the effect of taking:
public enum Days
{
[Display(Name = "Monday")]
Monday,
//Left out other days for brevity
[Display(Name = "Saturday")]
Saturday
}
and, combined with the rest of the model:
[Mandatory(ErrorMessage = "Please select at least one day")]
[Display(Name = "What are the best days to contact you (select one or more days)?")]
[ContactClientDaySelector(BulkSelectionThreshold = 6)]
public List<string> ContactClientDayCheckBox { get; set; }
along with code for `[ContactClientDaySelector]:
public class ContactClientDaySelectorAttribute : SelectorAttribute
{
public override IEnumerable<SelectListItem> GetItems()
{
return Selector.GetItemsFromEnum<ContactClientDay>();
}
}
would display a checkbox list with "Monday, ..., Saturday", by calling in the view thusly:
#Html.FullFieldEditor(m => m.QuotePartRecord.ContactClientDayCheckBox)
Note: FullFieldEditor is a special helper that iterates through the enum and using the BulkSelectionThreshold would pick either a radiobutton list, dropdown list, checkbox list or multi-select list -- in this case "6" would trigger the creation of a checkbox list because my enum had a collection of 6 items (i.e., days).
My controller only checks validity of model state and passes on to the confirmation view:
public ActionResult WrapUp(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("ExpenseInformation");
else if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("Confirm");
else
return View(quoteData);
}
public ActionResult Confirm(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("WrapUp");
else if ((nextButton != null) && ModelState.IsValid)
{
var quoteConfirm = _quoteService.CreateQuote(quoteData.QuotePartRecord);
return RedirectToAction("Submitted");
}
else
return View(quoteData);
}
Now, for posting the checkboxes selected by the user, I used the following in a confirmation view page:
[ContactClientDaySelector]
[ReadOnly(true)]
public List<string> ContactClientDayCheckBoxPost
{
get { return ContactClientDayCheckBox; }
}
This, combined with the DelimitedStringHelper would display the selections. For example, if Monday and Tuesday were both selected by the user, the Post would display "Monday, Tuesday".
However, I had to change my code around a bit and, and use int instead specifically for checkboxes (long story short: using NHibernate generates a cast error because of the use of List<string>, and this was a way around that problem).
I had to remove the enum and replaced it with this class:
public class ContactClientDay
{
public int Id { get; set; }
public string Name { get; set; }
}
Then I modified my Selector class thusly:
public class ContactClientDaySelectorAttribute : SelectorAttribute
{
public ContactClientDaySelectorAttribute()
{
//For checkboxes, number must equal amount of items
BulkSelectionThreshold = 6;
}
public override IEnumerable<SelectListItem> GetItems()
{
var contactClientDay = new List<ContactClientDay>
{
new ContactClientDay {Id = 1, Name = "Monday"},
//Left out other days for brevity
new ContactClientDay {Id = 6, Name = "Saturday"},
};
return contactClientDay.ToSelectList(m => m.Id, m => m.Name);
}
}
I changed my model to this:
public virtual int? ContactClientDayCheckBox { get; set; }
I changed my Post to this:
[ReadOnly(true)]
public string ContactClientDayCheckBoxPost
{
get { return QuotePartRecord.ContactClientDayCheckBox.ToString(); }
}
If I do public int? ContactClientDayCheckBoxPost instead, nothing would be displayed on the confirmation page.
If I instead used public string ContactClientDayCheckBoxPost and then did ContactClientDayCheckBox.ToString(), it would only display the "Name" of the first value selected ("Monday" only, not "Monday, Tuesday").
I cannot figure out how to programatically convert a sequence of int in this scenario (possibly with an extension)? Any thoughts/examples? Thanks in advance.
For reference, here is the extension for DelimitedStringHelper I am using:
public static class DelimitedStringHelper
{
public static string DefaultDelimiter = ", ";
/// <summary>
/// Convert a sequence of items to a delimited string. By default, ToString() will be called on each item in the sequence to formulate the result. The default delimiter of ', ' will be used
/// </summary>
public static string ToDelimitedString<T>(this IEnumerable<T> source)
{
return source.ToDelimitedString(x => x.ToString(), DefaultDelimiter);
}
/// <summary>
/// Convert a sequence of items to a delimited string. By default, ToString() will be called on each item in the sequence to formulate the result
/// </summary>
/// <param name="delimiter">The delimiter to separate each item with</param>
public static string ToDelimitedString<T>(this IEnumerable<T> source, string delimiter)
{
return source.ToDelimitedString(x => x.ToString(), delimiter);
}
/// <summary>
/// Convert a sequence of items to a delimited string. The default delimiter of ', ' will be used
/// </summary>
/// <param name="selector">A lambda expression to select a string property of <typeparamref name="T"/></param>
public static string ToDelimitedString<T>(this IEnumerable<T> source, Func<T, string> selector)
{
return source.ToDelimitedString(selector, DefaultDelimiter);
}
/// <summary>
/// Convert a sequence of items to a delimited string.
/// </summary>
/// <param name="selector">A lambda expression to select a string property of <typeparamref name="T"/></param>
/// <param name="delimiter">The delimiter to separate each item with</param>
public static string ToDelimitedString<T>(this IEnumerable<T> source, Func<T, string> selector, string delimiter)
{
if (source == null)
return string.Empty;
if (selector == null)
throw new ArgumentNullException("selector", "Must provide a valid property selector");
if (string.IsNullOrEmpty(delimiter))
delimiter = DefaultDelimiter;
return string.Join(delimiter, source.Select(selector).ToArray());
}
}

Related

Optimize algorithm: Update a collection by other collection with order C#

Problem: I have a collection need to be update by other collection. The length of them are not equal and each item are not same structure. But both of them are have identity field to detect same.
I write a generic algorithm to use everywhere but the complexity are O(m*n*3). The main problem is: Are there any better optimization algorithm? The input must should be generic as IList<T> and delegate to reusable.
Current approach:
Source: Data collection is used to display on GUI.
Target: Data collection which is got from remote server.
Remove old items from Source which doesn't existed from Target via delegate comparer.
Add new items from Target to Source.
Reorder Source by Target.
Scenario Usage: If you have an application display about 1~2k row on list view. And you have interval timer update this list item (about 1 seconds). The main point here application should be smooth, doesn't flick , keep state of object: Selected, change data... etc.
Here is source code:
using System;
using System.Collections.Generic;
using System.Linq;
namespace UpdateCollection
{
/// <summary>
/// Person DTO (from other 3rd assembly) to get data from their remote server.
/// </summary>
public class PersonDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
/// <summary>
/// Person model is used to display on GUI (our application).
/// </summary>
public class PersonModel
{
public string Identity { get; set; }
public string DisplayName { get; set; }
public int? Age { get; set; }
public bool Selected { get; set; }
public override string ToString()
{
return string.Format("{0} {1} {2} {3}", Identity, DisplayName, Age, Selected);
}
}
static class Program
{
/// <summary>
/// Encapsulates a method that has two parameters and does not return a value.
/// </summary>
/// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.This type parameter is contravariant. That is, you can use either the type you specified or any type that is less derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics.</typeparam>
/// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
public delegate void RefAction<T1, in T2>(ref T1 arg1, T2 arg2);
/// TODO: The complexity of algorithm is: O(m*n*3) Need to be optimization. For example: m*log2(n)+ m + n
/// <summary>
/// Update source by target.
/// </summary>
/// <typeparam name="TSourceType"></typeparam>
/// <typeparam name="TTargetType"></typeparam>
/// <param name="source">Source collection.</param>
/// <param name="target">Target collection.</param>
/// <param name="compare">Comparing method between source and target.</param>
/// <param name="convert">Convert method</param>
/// <param name="update">Update method</param>
/// <param name="remove">Remove method</param>
public static void UpdateBy<TSourceType, TTargetType>(
this IList<TSourceType> source,
IList<TTargetType> target,
Func<TSourceType, TTargetType, bool> compare,
Func<TTargetType, TSourceType> convert,
RefAction<TSourceType, TTargetType> update,
Func<TSourceType, bool> remove = null)
{
if (source == null || target == null)
return;
if (convert == null)
throw new AggregateException("convert");
if (compare == null)
throw new ArgumentNullException("compare");
// Remove item
for (var index = 0; index < source.Count; ++index)
{
if (target.Any(c => compare(source[index], c))) continue;
var temp = source[index];
if (remove == null)
source.RemoveAt(index--);
else if (remove(temp))
source.RemoveAt(index--);
}
// Add new item
foreach (var t in target.Where(t => !source.Any(c => compare(c, t))))
{
source.Add(convert(t));
}
// Sort by target
for (var index = 0; index < target.Count; ++index)
{
for (var pos = 0; pos < source.Count; ++pos)
{
if (!compare(source[pos], target[index])) continue;
var temp = source[pos];
if (update != null)
update(ref temp, target[index]);
source[pos] = temp;
if (pos == index) continue;
temp = source[pos];
source[pos] = source[index];
source[index] = temp;
}
}
}
public static IList<PersonModel> GetFromUserInterface()
{
return new List<PersonModel>
{
new PersonModel {Identity = "1", DisplayName = "a",},
new PersonModel {Identity = "2", DisplayName = "b", Selected = true},
new PersonModel {Identity = "3", DisplayName = "c", Selected = true},
new PersonModel {Identity = "4", DisplayName = "D"}
};
}
public static IList<PersonDto> GetFromRemoteServer()
{
return new List<PersonDto>
{
new PersonDto {Id = 6, Name = "F", Birthday = DateTime.Parse("1984-01-02")},
new PersonDto {Id = 4, Name = "D", Birthday = DateTime.Parse("1986-01-12")},
new PersonDto {Id = 3, Name = "C", Birthday = DateTime.Parse("1982-03-05")},
new PersonDto {Id = 5, Name = "E", Birthday = DateTime.Parse("1984-05-22")},
new PersonDto {Id = 1, Name = "A", Birthday = DateTime.Parse("1986-02-14")}
};
}
public static bool Compare(PersonModel source, PersonDto target)
{
return source.Identity == target.Id.ToString();
}
public static PersonModel Convert(PersonDto target)
{
return new PersonModel
{
Identity = target.Id.ToString(),
Age = target.Birthday.Year,
DisplayName = target.Name,
};
}
public static void Update(ref PersonModel source, PersonDto target)
{
source.Age = target.Birthday.Year;
source.DisplayName = target.Name;
}
static void Main(string[] args)
{
var source = GetFromUserInterface();
var target = GetFromRemoteServer();
Console.WriteLine("==> Before Update:\r\n");
foreach (var item in source)
Console.Write("{0}\r\n\r\n", item);
// TODO: How to optimize UpdateBy algorithm to better?
source.UpdateBy(target, Compare, Convert, Update);
Console.WriteLine("==> After Update:\r\n");
foreach (var item in source)
Console.Write("{0}\r\n\r\n", item);
Console.ReadLine();
}
}
}
You are storing your data in a list; switching to a hash table structure would give you (roughly) constant time access to the members by key. If the ordering of the data is important, many languages have some kind of ordered hash construct you can use.
Finally, I found the best algorithm for this issue. Base on QuickSort algorithm. The procedure are:
Make lookup index base on Target collection.
Sort Source base on Target index with QuickSort algorithm. Keep track index of item from Source which doesn't exist in Target
Remove item from Source.
Add/update item from Target to Source
The complexity or algorithm is: O(m * 2 + n + n * log2(n))
Here is source code:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace UpdateCollection
{
static class CollectionUpdater
{
/// <summary>
/// Encapsulates a method that has two parameters and does not return a value.
/// </summary>
/// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.This type parameter is contravariant. That is, you can use either the type you specified or any type that is less derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics.</typeparam>
/// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
public delegate void RefAction<T1, in T2>(ref T1 arg1, T2 arg2);
/// <summary>
/// Update source collection by target collection.
/// </summary>
/// <remarks>The complexity of algorithm is: O(m * 2 + n + n * log2(n))</remarks>
/// <typeparam name="TSourceType">Source data type.</typeparam>
/// <typeparam name="TTargetType">Target data type.</typeparam>
/// <typeparam name="TIdentity"></typeparam>
/// <param name="source">The source collection.</param>
/// <param name="target">The target collection.</param>
/// <param name="targetIdentity">Convert target to identity.</param>
/// <param name="sourceIdentity">Convert source to identity.</param>
/// <param name="targetToSourceConverter">Convert target to source.</param>
/// <param name="sourceUpdater">Update source from target.</param>
/// <param name="sourceRemover">Remove source item.</param>
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
[SuppressMessage("ReSharper", "AccessToModifiedClosure")]
public static void UpdateBy<TSourceType, TTargetType, TIdentity>(
this IList<TSourceType> source,
IEnumerable<TTargetType> target,
Func<TTargetType, TIdentity> targetIdentity,
Func<TSourceType, TIdentity> sourceIdentity,
Func<TTargetType, TSourceType> targetToSourceConverter,
RefAction<TSourceType, TTargetType> sourceUpdater,
Func<TSourceType, bool> sourceRemover = null) where TIdentity : IComparable<TIdentity>
{
// Step 1: Make index of target. O(m)
// Index, target, order.
var targetOrderLookup = new Dictionary<TIdentity, Tuple<TTargetType, int>>();
do
{
var counterIndex = 0;
foreach (var item in target)
{
targetOrderLookup.Add(targetIdentity(item), Tuple.Create(item, counterIndex));
++counterIndex;
}
} while (false);
var skipRemoveIdentity = new HashSet<TIdentity>();
if (source.Count != 0)
{
// Step 2: Re-implement quick-sort.
Action<IList<TSourceType>, int, int, Comparison<Tuple<TSourceType, int>>> quickSort = null;
var removeIdentityLockup = new Dictionary<TIdentity, int>();
quickSort = (elements, left, right, comparer) =>
{
var i = left;
var j = right;
var pivotIndex = (left + right) / 2;
var pivot = elements[pivotIndex];
while (i <= j)
{
while (comparer(Tuple.Create(elements[i], i), Tuple.Create(pivot, pivotIndex)) < 0)
i++;
while (comparer(Tuple.Create(elements[j], j), Tuple.Create(pivot, pivotIndex)) > 0)
j--;
if (i <= j)
{
var leftId = sourceIdentity(elements[i]);
var rightId = sourceIdentity(elements[j]);
var leftValue = targetOrderLookup.ContainsKey(leftId);
var rightValue = targetOrderLookup.ContainsKey(rightId);
if (!leftValue)
removeIdentityLockup[leftId] = j;
if (!rightValue)
removeIdentityLockup[rightId] = i;
// Swap
var tmp = elements[i];
elements[i] = elements[j];
elements[j] = tmp;
i++;
j--;
}
}
// Recursive calls
if (left < j)
quickSort(elements, left, j, comparer);
if (i < right)
quickSort(elements, i, right, comparer);
};
// Step 2: Sort source. O(log2(n))
quickSort(source, 0, source.Count - 1, (c, d) =>
{
var leftId = sourceIdentity(c.Item1);
var rightId = sourceIdentity(d.Item1);
Tuple<TTargetType, int> leftValue;
if (!targetOrderLookup.TryGetValue(leftId, out leftValue))
removeIdentityLockup[leftId] = c.Item2;
Tuple<TTargetType, int> rightValue;
if (!targetOrderLookup.TryGetValue(rightId, out rightValue))
removeIdentityLockup[rightId] = d.Item2;
if (leftValue == null && rightValue == null)
return 0;
if (leftValue == null)
return -1;
if (rightValue == null)
return 1;
return leftValue.Item2.CompareTo(rightValue.Item2);
});
// Remove item
foreach (KeyValuePair<TIdentity, int> item in removeIdentityLockup.OrderByDescending(v => v.Value))
{
if (sourceRemover == null)
{
if (source.IsReadOnly)
skipRemoveIdentity.Add(item.Key);
else
{
source.RemoveAt(item.Value);
}
}
else
{
if (sourceRemover(source[item.Value]))
{
if (source.IsReadOnly)
skipRemoveIdentity.Add(item.Key);
else
source.RemoveAt(item.Value);
}
else // Keep can remove, avoid update
skipRemoveIdentity.Add(item.Key);
}
}
}
// Add new item
var sourceIndex = 0;
foreach (var item in target)
{
var targetItem = item;
if (sourceIndex < source.Count)
{
var sourceItem = source[sourceIndex];
var sourceId = sourceIdentity(sourceItem);
var targetId = targetIdentity(targetItem);
while (skipRemoveIdentity.Contains(sourceId) && sourceIndex < source.Count)
{
++sourceIndex;
sourceItem = source[sourceIndex];
sourceId = sourceIdentity(sourceItem);
}
if (sourceIndex < source.Count)
{
if (sourceId.CompareTo(targetId) == 0) // Update source
{
sourceUpdater(ref sourceItem, targetItem);
source[sourceIndex] = sourceItem;
++sourceIndex;
}
else // Insert new
{
if (source.IsReadOnly) continue;
source.Insert(sourceIndex, targetToSourceConverter(targetItem));
++sourceIndex;
}
}
else
{
if (source.IsReadOnly) continue;
source.Add(targetToSourceConverter(targetItem));
}
}
else
{
if (source.IsReadOnly) continue;
source.Add(targetToSourceConverter(targetItem));
++sourceIndex;
}
}
}
/// <summary>
/// Person DTO (from other 3rd assembly) to get data from their remote server.
/// </summary>
public class PersonDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
/// <summary>
/// Person model is used to display on GUI (our application).
/// </summary>
public class PersonModel
{
public string Identity { get; set; }
public string DisplayName { get; set; }
public int? Age { get; set; }
public bool Selected { get; set; }
public override string ToString()
{
return string.Format("\"{0}\" {1} {2} {3}", Identity, DisplayName, Age, Selected ? "selected" : string.Empty);
}
}
/// <summary>
/// Get from user interface, it work for both fix & non-fixed collection.
/// </summary>
public static IList<PersonModel> GetFromUserInterface()
{
//return new List<PersonModel> // For non-fixed collection. Add/remove/update support
return new[] // For fix collection. Just update support
{
new PersonModel {Identity = "4", DisplayName = "D"},
new PersonModel {Identity = "13", DisplayName = "", Selected = true}, // Must remove.
new PersonModel {Identity = "1", DisplayName = "a",},
new PersonModel {Identity = "10", DisplayName = "", Selected = true}, // Must remove.
new PersonModel {Identity = "3", DisplayName = "c", Selected = true},
new PersonModel {Identity = "9", DisplayName = "", Selected = true}, // Must remove.
new PersonModel {Identity = "2", DisplayName = "", Selected = true} // Must remove.
};
}
/// <summary>
/// Get from remote service.
/// </summary>
public static IEnumerable<PersonDto> GetFromRemoteServer()
{
return new List<PersonDto>
{
new PersonDto {Id = 6, Name = "F", Birthday = DateTime.Parse("1984-01-02")}, // Must add
new PersonDto {Id = 4, Name = "D", Birthday = DateTime.Parse("1986-01-12")},
new PersonDto {Id = 3, Name = "C", Birthday = DateTime.Parse("1982-03-05")},
new PersonDto {Id = 5, Name = "E", Birthday = DateTime.Parse("1984-05-22")}, // Must Add
new PersonDto {Id = 1, Name = "A", Birthday = DateTime.Parse("1986-02-14")}
};
}
/// <summary>
/// Convert target to source.
/// </summary>
public static PersonModel Convert(PersonDto target)
{
return new PersonModel
{
Identity = target.Id.ToString(),
Age = DateTime.Now.Year - target.Birthday.Year,
DisplayName = target.Name,
};
}
/// <summary>
/// Update target from source.
/// </summary>
public static void Update(ref PersonModel source, PersonDto target)
{
source.Age = DateTime.Now.Year - target.Birthday.Year;
source.DisplayName = target.Name;
}
/// <summary>
/// Get identity.
/// </summary>
public static string Identity(PersonModel arg)
{
return arg.Identity;
}
/// <summary>
/// Get identity.
/// </summary>
public static string Identity(PersonDto arg)
{
return arg.Id.ToString();
}
static void Main()
{
var source = GetFromUserInterface();
var target = GetFromRemoteServer();
Console.WriteLine("==> Before Update:\r\n");
foreach (var item in source)
Console.Write("{0}\r\n\r\n", item);
// TODO: Update source collection by target.
source.UpdateBy(target, Identity, Identity, Convert, Update);
Console.WriteLine("==> After Update:\r\n");
foreach (var item in source)
Console.Write("{0}\r\n\r\n", item);
Console.ReadLine();
}
}
}

How to compare two lists of objects in C#?

Lets say I have a list of objects:
public class MyObject
{
public int ID {get;set;}
public string Name {get;set;}
public DateTime StartDate {get;set;}
}
now the data:
{0, "my", DateTime.Now}
{0, "List", DateTime.Now}
{0, "Data", DateTime.Now}
Now say I stripped this data from an XML or Excel document and I want to compare with what is in the database and Ignore data that already exists. I am using Entity Framework to handle this.
public IEnumerable<MyObject> GetBy()
{
return _context.MyObjects.OrderBy(x => x.Name);
}
I know if you want to get data from a list where it is different you use:
var myNewStuff = myStuff.Except(myDataBaseStuff);
But none of the ID properties will match so that won't work
How do I compare the two lists BASED on the Name AND StartDate values?
You need to implement your own IEqualityComparer and use the overload of Except that takes it. Using Resharper, I generated this one:
public sealed class NameStartDateEqualityComparer : IEqualityComparer<MyObject>
{
public bool Equals(MyObject x, MyObject y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return string.Equals(x.Name, y.Name) && x.StartDate.Equals(y.StartDate);
}
public int GetHashCode(MyObject obj)
{
unchecked
{
return ((obj.Name != null ? obj.Name.GetHashCode() : 0)*397) ^ obj.StartDate.GetHashCode();
}
}
}
Notice that the comparer only examines the Name and StartDate properties. You can of course change the comparer to your liking, like truncating the milliseconds off of the StartDate before comparing them. Then use it as such:
var myNewStuff = myStuff.Except(myDataBaseStuff, new NameStartDateEqualityComparer());

Change a LINQ expression predicate from one type to another

I have two unrelated classes. One is exposed as API, and the other is used internally by 3rd party API.
Entity is exposed from our API, while EntityProvider is from the 3rd party assembly.
class Entity
{
public A { get; set; }
}
class EntityProvider
{
public A { get; set; }
}
Consumers of our API will provide predicates of the form Expression <Func<Entity, bool>> and I need to modify it to Expression <Func<EntityProvider, bool>> so that I can pass the same to internal 3rd party assembly.
Please help with this conversion.
Since Expressions in .NET are immutable, the only way to do this is to rebuild the whole expression. To do this usually involves inheriting from the ExpressionVisitor class. Depending on the complexity of the expressions you have to convert this could be quite complicated.
This is a simple example of a visitor that will work with simple expressions( like x=>x.Someproperty == somevalue ). It's just an example to get you started and it's in no way finished or tested(it won't handle method calls in the expression for example)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//Type from which to convert
public class A
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
//Type to which we want the Expression converted
public class B
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
class Program
{
static void Main(string[] args)
{
//the expression we want to convert expresion
Expression<Func<A, bool>> expA = x => x.Property1 == 6 && x.Property2 == 3;
var visitor = new ParameterTypeVisitor<A,B>(expA);
var expB = visitor.Convert();
var b = new B() { Property1 = 6, Property2 = 3 };
//try the converted expression
var result = expB.Compile().Invoke(b);
}
}
public class ParameterTypeVisitor<TFrom,TTo> : ExpressionVisitor
{
private Dictionary<string, ParameterExpression> convertedParameters;
private Expression<Func<TFrom, bool>> expression;
public ParameterTypeVisitor(Expression<Func<TFrom,bool>> expresionToConvert )
{
//for each parameter in the original expression creates a new parameter with the same name but with changed type
convertedParameters = expresionToConvert.Parameters
.ToDictionary(
x => x.Name,
x => Expression.Parameter(typeof (TTo), x.Name)
);
expression = expresionToConvert;
}
public Expression<Func<TTo,bool>> Convert()
{
return (Expression<Func<TTo, bool>>)Visit(expression);
}
//handles Properties and Fields accessors
protected override Expression VisitMember(MemberExpression node)
{
//we want to replace only the nodes of type TFrom
//so we can handle expressions of the form x=> x.Property.SubProperty
//in the expression x=> x.Property1 == 6 && x.Property2 == 3
//this replaces ^^^^^^^^^^^ ^^^^^^^^^^^
if (node.Member.DeclaringType == typeof(TFrom))
{
//gets the memberinfo from type TTo that matches the member of type TFrom
var memeberInfo = typeof (TTo).GetMember(node.Member.Name).First();
//this will actually call the VisitParameter method in this class
var newExp = Visit(node.Expression);
return Expression.MakeMemberAccess(newExp, memeberInfo);
}
else
{
return base.VisitMember(node);
}
}
// this will be called where ever we have a reference to a parameter in the expression
// for ex. in the expression x=> x.Property1 == 6 && x.Property2 == 3
// this will be called twice ^ ^
protected override Expression VisitParameter(ParameterExpression node)
{
var newParameter = convertedParameters[node.Name];
return newParameter;
}
//this will be the first Visit method to be called
//since we're converting LamdaExpressions
protected override Expression VisitLambda<T>(Expression<T> node)
{
//visit the body of the lambda, this will Traverse the ExpressionTree
//and recursively replace parts of the expression we for which we have matching Visit methods
var newExp = Visit(node.Body);
//this will create the new expression
return Expression.Lambda(newExp,convertedParameters.Select(x=>x.Value));
}
}

Dynamic LINQ Expression for sorting navigation property

MVC3, Entity Framework 4.1 Code first.
Working with 2 tables
Model:
public class UniversityMaster
{
[Key]
public string UniversityId { get; set; }
public string UniversityName { get; set; }
}
public class ProgramMaster
{
[Key]
public string ProgramId { get; set; }
public string ProgramName { get; set; }
public string UniversityId { get; set; }
public virtual UniversityMaster University { get; set; } // navigation property
}
Dynamic expression for sorting (just to avoid a switch case statement):
public virtual IQueryable< ProgramMaster > GetQueryableSort(string sortField="", string sortDirection="")
{
IQueryable<ProgramMaster> query = _dbSet;
ParameterExpression pe = Expression.Parameter(typeof(ProgramMaster), string.Empty);
MemberExpression property = Expression.PropertyOrField(pe, sortField);
//get a exception here if the sort field is of navigation property (University.UniversityName)
LambdaExpression lambda = Expression.Lambda(property, pe);
if (sortDirection == "ASC")
orderbydir = "OrderBy";
else
orderbydir = "OrderByDescending";
MethodCallExpression call = Expression.Call(typeof(Queryable),
orderbydir, new Type[] { typeof(TEntity), property.Type }, query.Expression, Expression.Quote(lambda));
var returnquery = (IOrderedQueryable<ProgramMaster>)query.Provider.CreateQuery< ProgramMaster >(call);
return returnquery;
}
The page is displaying a grid with two columns Program Name and University Name using webgrid. The sorting work fine for Program Name column, however fails if sorted by University Name as this property is in UniversityMaster and the Expression.PropertyOrField searches this property in ProgramMaster. Here is the exception:
University.UniversityName' is not a member of type 'App.Core.Model.ProgramMaster
My question is how I make this work for navigation properties of my model class.
Hope I was able explain the scenario. Any help is appreciated.
Well that's because the MemberExpression is trying to call a member named Univerty.UniversityName on the parameter. What you want to do is call a member named Univerity on the parameter, then call UniversityName on that. Effectively, you need to iteratively resolve the property names.
public virtual IQueryable< ProgramMaster > GetQueryableSort(string sortField = "", string sortDirection = "")
{
IQueryable<ProgramMaster> query = _dbSet;
var propertyNames = sortField.Split(".");
ParameterExpression pe = Expression.Parameter(typeof(ProgramMaster), string.Empty);
Expression property = pe;
foreach(var prop in propertyName)
{
property = Expression.PropertyOrField(property, prop);
}
LambdaExpression lambda = Expression.Lambda(property, pe);
if (sortDirection == "ASC")
orderbydir = "OrderBy";
else
orderbydir = "OrderByDescending";
MethodCallExpression call = Expression.Call(
typeof(Queryable),
orderbydir,
new Type[] { typeof(TEntity), property.Type },
query.Expression,
Expression.Quote(lambda));
var returnquery = (IOrderedQueryable<ProgramMaster>)query.Provider.CreateQuery<ProgramMaster>(call);
return returnquery;
}
Microsoft has a DynamicQueryable class which can be used to dynamically construct certain portions of a LINQ query using strings. With this you can say myQuery.OrderBy("University.UniversityName") and it will handle building the expression. The same library also supports dynamic construction of SELECT and WHERE clauses.
You can find a copy of the source as part of the excellent EntityFramework.Extended package by Loresoft. Microsoft's file is at https://github.com/loresoft/EntityFramework.Extended/blob/master/Source/EntityFramework.Extended/Dynamic/DynamicQueryable.cs

Linq Query Need - Looking for a pattern of data

Say I have a collection of the following simple class:
public class MyEntity
{
public string SubId { get; set; }
public System.DateTime ApplicationTime { get; set; }
public double? ThicknessMicrons { get; set; }
}
I need to search through the entire collection looking for 5 consecutive (not 5 total, but 5 consecutive) entities that have a null ThicknessMicrons value. Consecutiveness will be based on the ApplicationTime property. The collection will be sorted on that property.
How can I do this in a Linq query?
You can write your own extension method pretty easily:
public static IEnumerable<IEnumerable<T>> FindSequences<T>(this IEnumerable<T> sequence, Predicate<T> selector, int size)
{
List<T> curSequence = new List<T>();
foreach (T item in sequence)
{
// Check if this item matches the condition
if (selector(item))
{
// It does, so store it
curSequence.Add(item);
// Check if the list size has met the desired size
if (curSequence.Count == size)
{
// It did, so yield that list, and reset
yield return curSequence;
curSequence = new List<T>();
}
}
else
{
// No match, so reset the list
curSequence = new List<T>();
}
}
}
Now you can just say:
var groupsOfFive = entities.OrderBy(x => x.ApplicationTime)
.FindSequences(x => x.ThicknessMicrons == null, 5);
Note that this will return all sub-sequences of length 5. You can test for the existence of one like so:
bool isFiveSubsequence = groupsOfFive.Any();
Another important note is that if you have 9 consecutive matches, only one sub-sequence will be located.

Resources