强类型动态Linq分类
我正在尝试构建一些用于动态分类Linq IQueryable <>的代码。
显而易见的方法是在这里,它使用字段名称的字符串对列表进行排序
http://dvanderboom.wordpress.com/2008/12/19/dynamically-composing-linq-orderby-clauses/
不过,我想要一个更改 - 编译时间检查字段名称,以及使用重构/查找所有引用来支持以后的维护。 这意味着我想将字段定义为f => f.Name,而不是字符串。
对于我的具体用途,我想封装一些代码,根据用户输入决定应该使用哪个名为“OrderBy”表达式的列表,而不用每次编写不同的代码。
这是我写的内容的要点:
var list = from m Movies select m; // Get our list
var sorter = list.GetSorter(...); // Pass in some global user settings object
sorter.AddSort("NAME", m=>m.Name);
sorter.AddSort("YEAR", m=>m.Year).ThenBy(m=>m.Year);
list = sorter.GetSortedList();
...
public class Sorter<TSource>
...
public static Sorter<TSource> GetSorter(this IQueryable<TSource> source, ...)
GetSortedList函数确定要使用哪个指定排序,这会导致List对象,其中每个FieldData都包含在AddSort中传递的字段的MethodInfo和Type值:
public SorterItem<TSource> AddSort(Func<T, TKey> field)
{
MethodInfo ... = field.Method;
Type ... = TypeOf(TKey);
// Create item, add item to diction, add fields to item's List<>
// The item has the ThenBy method, which just adds another field to the List<>
}
我不确定是否有办法以一种方式存储整个字段对象,以便稍后返回它(因为它是一个泛型类型,所以不可能投射)
有没有一种方法可以调整示例代码,或者提出全新的代码,以便在强类型字段名称存储在某个容器中并进行检索后对其进行排序(丢失任何泛型类型转换)
最简单的方法是让AddSort()函数接受一个Expression <Func <Movie >>而不仅仅是一个Func。 这允许您的排序方法检查表达式以提取您想要排序的属性的名称。 然后,您可以将该名称作为字符串在内部存储,因此存储起来非常简单,您可以使用链接的排序算法,但是您还可以通过类型安全性和编译时间检查有效的属性名称。
static void Main(string[] args)
{
var query = from m in Movies select m;
var sorter = new Sorter<Movie>();
sorter.AddSort("NAME", m => m.Name);
}
class Sorter<T>
{
public void AddSort(string name, Expression<Func<T, object>> func)
{
string fieldName = (func.Body as MemberExpression).Member.Name;
}
}
在这种情况下,我将对象用作func的返回类型,因为它很容易自动转换,但如果需要更多功能,则可以根据需要使用不同类型或泛型实现。 在这种情况下,由于Expression只是在那里被检查,所以它并不重要。
另一种可能的方法是继续使用Func,并将其存储在字典中。 然后,当涉及到排序时,您需要获得排序的值,您可以调用如下所示的内容:
// assuming a dictionary of fields to sort for, called m_fields
m_fields[fieldName](currentItem)
坏消息! 我必须学习如何从头到尾阅读规范:-(
然而,现在我花费了太多的时间而不是工作,我会发布我的结果,希望这会激励人们阅读,思考,理解(重要),然后采取行动。 或者如何用泛型,lambda和有趣的Linq东西太聪明。
我在这个练习中发现的一个巧妙的技巧,就是那些派生自Dictionary
私人内部类。 他们的全部目的是删除所有尖括号以提高可读性。
哦,差点忘了代码:
更新:使代码通用,并使用IQueryable
而不是IEnumerable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
namespace StackOverflow.StrongTypedLinqSort
{
[TestFixture]
public class SpecifyUserDefinedSorting
{
private Sorter<Movie> sorter;
[SetUp]
public void Setup()
{
var unsorted = from m in Movies select m;
sorter = new Sorter<Movie>(unsorted);
sorter.Define("NAME", m1 => m1.Name);
sorter.Define("YEAR", m2 => m2.Year);
}
[Test]
public void SortByNameThenYear()
{
var sorted = sorter.SortBy("NAME", "YEAR");
var movies = sorted.ToArray();
Assert.That(movies[0].Name, Is.EqualTo("A"));
Assert.That(movies[0].Year, Is.EqualTo(2000));
Assert.That(movies[1].Year, Is.EqualTo(2001));
Assert.That(movies[2].Name, Is.EqualTo("B"));
}
[Test]
public void SortByYearThenName()
{
var sorted = sorter.SortBy("YEAR", "NAME");
var movies = sorted.ToArray();
Assert.That(movies[0].Name, Is.EqualTo("B"));
Assert.That(movies[1].Year, Is.EqualTo(2000));
}
[Test]
public void SortByYearOnly()
{
var sorted = sorter.SortBy("YEAR");
var movies = sorted.ToArray();
Assert.That(movies[0].Name, Is.EqualTo("B"));
}
private static IQueryable<Movie> Movies
{
get { return CreateMovies().AsQueryable(); }
}
private static IEnumerable<Movie> CreateMovies()
{
yield return new Movie {Name = "B", Year = 1990};
yield return new Movie {Name = "A", Year = 2001};
yield return new Movie {Name = "A", Year = 2000};
}
}
internal class Sorter<E>
{
public Sorter(IQueryable<E> unsorted)
{
this.unsorted = unsorted;
}
public void Define<P>(string name, Expression<Func<E, P>> selector)
{
firstPasses.Add(name, s => s.OrderBy(selector));
nextPasses.Add(name, s => s.ThenBy(selector));
}
public IOrderedQueryable<E> SortBy(params string[] names)
{
IOrderedQueryable<E> result = null;
foreach (var name in names)
{
result = result == null
? SortFirst(name, unsorted)
: SortNext(name, result);
}
return result;
}
private IOrderedQueryable<E> SortFirst(string name, IQueryable<E> source)
{
return firstPasses[name].Invoke(source);
}
private IOrderedQueryable<E> SortNext(string name, IOrderedQueryable<E> source)
{
return nextPasses[name].Invoke(source);
}
private readonly IQueryable<E> unsorted;
private readonly FirstPasses firstPasses = new FirstPasses();
private readonly NextPasses nextPasses = new NextPasses();
private class FirstPasses : Dictionary<string, Func<IQueryable<E>, IOrderedQueryable<E>>> {}
private class NextPasses : Dictionary<string, Func<IOrderedQueryable<E>, IOrderedQueryable<E>>> {}
}
internal class Movie
{
public string Name { get; set; }
public int Year { get; set; }
}
}
根据每个人的贡献,我提出了以下建议。
它提供了双向排序以及内部解决问题。 这意味着我不需要为给定类型的每个未排序列表创建一个新的排序器。 为什么不能将未排序的列表传递给分拣机。 这意味着我们可以为不同类型的Sorter创建一个signelton实例...
只是一个想法:
[TestClass]
public class SpecifyUserDefinedSorting
{
private Sorter<Movie> sorter;
private IQueryable<Movie> unsorted;
[TestInitialize]
public void Setup()
{
unsorted = from m in Movies select m;
sorter = new Sorter<Movie>();
sorter.Register("Name", m1 => m1.Name);
sorter.Register("Year", m2 => m2.Year);
}
[TestMethod]
public void SortByNameThenYear()
{
var instructions = new List<SortInstrcution>()
{
new SortInstrcution() {Name = "Name"},
new SortInstrcution() {Name = "Year"}
};
var sorted = sorter.SortBy(unsorted, instructions);
var movies = sorted.ToArray();
Assert.AreEqual(movies[0].Name, "A");
Assert.AreEqual(movies[0].Year, 2000);
Assert.AreEqual(movies[1].Year, 2001);
Assert.AreEqual(movies[2].Name, "B");
}
[TestMethod]
public void SortByNameThenYearDesc()
{
var instructions = new List<SortInstrcution>()
{
new SortInstrcution() {Name = "Name", Direction = SortDirection.Descending},
new SortInstrcution() {Name = "Year", Direction = SortDirection.Descending}
};
var sorted = sorter.SortBy(unsorted, instructions);
var movies = sorted.ToArray();
Assert.AreEqual(movies[0].Name, "B");
Assert.AreEqual(movies[0].Year, 1990);
Assert.AreEqual(movies[1].Name, "A");
Assert.AreEqual(movies[1].Year, 2001);
Assert.AreEqual(movies[2].Name, "A");
Assert.AreEqual(movies[2].Year, 2000);
}
[TestMethod]
public void SortByNameThenYearDescAlt()
{
var instructions = new List<SortInstrcution>()
{
new SortInstrcution() {Name = "Name", Direction = SortDirection.Descending},
new SortInstrcution() {Name = "Year"}
};
var sorted = sorter.SortBy(unsorted, instructions);
var movies = sorted.ToArray();
Assert.AreEqual(movies[0].Name, "B");
Assert.AreEqual(movies[0].Year, 1990);
Assert.AreEqual(movies[1].Name, "A");
Assert.AreEqual(movies[1].Year, 2000);
Assert.AreEqual(movies[2].Name, "A");
Assert.AreEqual(movies[2].Year, 2001);
}
[TestMethod]
public void SortByYearThenName()
{
var instructions = new List<SortInstrcution>()
{
new SortInstrcution() {Name = "Year"},
new SortInstrcution() {Name = "Name"}
};
var sorted = sorter.SortBy(unsorted, instructions);
var movies = sorted.ToArray();
Assert.AreEqual(movies[0].Name, "B");
Assert.AreEqual(movies[1].Year, 2000);
}
[TestMethod]
public void SortByYearOnly()
{
var instructions = new List<SortInstrcution>()
{
new SortInstrcution() {Name = "Year"}
};
var sorted = sorter.SortBy(unsorted, instructions);
var movies = sorted.ToArray();
Assert.AreEqual(movies[0].Name, "B");
}
private static IQueryable<Movie> Movies
{
get { return CreateMovies().AsQueryable(); }
}
private static IEnumerable<Movie> CreateMovies()
{
yield return new Movie { Name = "B", Year = 1990 };
yield return new Movie { Name = "A", Year = 2001 };
yield return new Movie { Name = "A", Year = 2000 };
}
}
public static class SorterExtension
{
public static IOrderedQueryable<T> SortBy<T>(this IQueryable<T> source, Sorter<T> sorter, IEnumerable<SortInstrcution> instrcutions)
{
return sorter.SortBy(source, instrcutions);
}
}
public class Sorter<TSource>
{
private readonly FirstPasses _FirstPasses;
private readonly FirstPasses _FirstDescendingPasses;
private readonly NextPasses _NextPasses;
private readonly NextPasses _NextDescendingPasses;
public Sorter()
{
this._FirstPasses = new FirstPasses();
this._FirstDescendingPasses = new FirstPasses();
this._NextPasses = new NextPasses();
this._NextDescendingPasses = new NextPasses();
}
public void Register<TKey>(string name, Expression<Func<TSource, TKey>> selector)
{
this._FirstPasses.Add(name, s => s.OrderBy(selector));
this._FirstDescendingPasses.Add(name, s => s.OrderByDescending(selector));
this._NextPasses.Add(name, s => s.ThenBy(selector));
this._NextDescendingPasses.Add(name, s => s.ThenByDescending(selector));
}
public IOrderedQueryable<TSource> SortBy(IQueryable<TSource> source, IEnumerable<SortInstrcution> instrcutions)
{
IOrderedQueryable<TSource> result = null;
foreach (var instrcution in instrcutions)
result = result == null ? this.SortFirst(instrcution, source) : this.SortNext(instrcution, result);
return result;
}
private IOrderedQueryable<TSource> SortFirst(SortInstrcution instrcution, IQueryable<TSource> source)
{
if (instrcution.Direction == SortDirection.Ascending)
return this._FirstPasses[instrcution.Name].Invoke(source);
return this._FirstDescendingPasses[instrcution.Name].Invoke(source);
}
private IOrderedQueryable<TSource> SortNext(SortInstrcution instrcution, IOrderedQueryable<TSource> source)
{
if (instrcution.Direction == SortDirection.Ascending)
return this._NextPasses[instrcution.Name].Invoke(source);
return this._NextDescendingPasses[instrcution.Name].Invoke(source);
}
private class FirstPasses : Dictionary<string, Func<IQueryable<TSource>, IOrderedQueryable<TSource>>> { }
private class NextPasses : Dictionary<string, Func<IOrderedQueryable<TSource>, IOrderedQueryable<TSource>>> { }
}
internal class Movie
{
public string Name { get; set; }
public int Year { get; set; }
}
public class SortInstrcution
{
public string Name { get; set; }
public SortDirection Direction { get; set; }
}
public enum SortDirection
{
//Note I have created this enum because the one that exists in the .net
// framework is in the web namespace...
Ascending,
Descending
}
请注意,如果您不想依赖SortInstrcution,则不会那么难改变。
希望这可以帮助某人。
链接地址: http://www.djcxy.com/p/34225.html