2009-02-17 18 views
24

मैं एक लिंक IQueryable <> गतिशील रूप से सॉर्ट करने के लिए कुछ कोड बनाने की कोशिश कर रहा हूं।मजबूत टाइप किए गए गतिशील लिंक सॉर्टिंग

स्पष्ट तरीका यहाँ है, जो क्षेत्र नाम के लिए एक स्ट्रिंग का उपयोग
http://dvanderboom.wordpress.com/2008/12/19/dynamically-composing-linq-orderby-clauses/

हालांकि मैं एक परिवर्तन चाहते हैं एक सूची सॉर्ट करता - फ़ील्ड नामों की जाँच समय संकलन, और पुनर्रचना के उपयोग करने की क्षमता/सभी का पता लगाएं बाद में रखरखाव का समर्थन करने के लिए संदर्भ। इसका मतलब है कि मैं फ़ील्ड को f => f.Name के रूप में परिभाषित करना चाहता हूं, स्ट्रिंग के बजाए।

मेरे विशिष्ट उपयोग के लिए मैं कुछ कोड को समाहित करना चाहता हूं जो तय करेगा कि "ऑर्डरबी" अभिव्यक्तियों की एक सूची का उपयोग उपयोगकर्ता इनपुट के आधार पर किया जाना चाहिए, हर बार अलग-अलग कोड लिखने के बिना।

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 समारोह जो नामित तरह की उपयोग करने के लिए निर्धारित करता है, जो एक सूची वस्तु है, जहां प्रत्येक FieldData MethodInfo और प्रकार मान में परिणाम:

यहाँ मैं क्या लिखा है का सार है AddSort में पारित क्षेत्रों में:

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<> 
} 

मैं अगर वहाँ एक तरीका है कि इसे बाद में वापस कर दी अनुमति होगी में पूरे क्षेत्र वस्तु स्टोर करने के लिए एक रास्ता है यकीन नहीं है (यह कास्ट करने के लिए, यह बाद से असंभव हो जाएगा एक सामान्य प्रकार है)

वहाँ एक रास्ता मैं नमूना कोड अनुकूलन सकता है, या आदेश वे कुछ कंटेनर में संग्रहित किया गया है और पुनः प्राप्त करने के बाद जोरदार टाइप किया फ़ील्ड नाम का उपयोग कर सॉर्ट करने के लिए में पूरी तरह से नए कोड के साथ आते हैं, है (किसी भी सामान्य प्रकार कास्टिंग खोने)

+2

पोस्ट देखें: (। यह अन्य जो चाहता है से "जादू" आदेश में मदद मिल सकती) http://stackoverflow.com/questions/41244/dynamic-linq-orderby – Nordes

उत्तर

17

ऐसा करने का सबसे आसान तरीका यह होगा कि आपके AddSort() फ़ंक्शन एक अभिव्यक्ति < Func < मूवी >> केवल एक 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 लेना है, और इसे स्वयं शब्दकोश में संग्रहीत करना है।

// assuming a dictionary of fields to sort for, called m_fields 
m_fields[fieldName](currentItem) 
+0

हाय Ch00k, यह कोड अद्भुत लग रहा है! ग्रुपबी के अलावा, मुझे वही ज़रूरत है ... क्या आप मेरी मदद करेंगे? धन्यवाद! – ibiza

+0

लेकिन प्रत्येक 'फ़ील्डनाम' के लिए ऑर्डर करने के लिए कोड क्या है? –

8

क्षमा: तब, जब यह छँटाई करने के लिए आता है, और आप पर सॉर्ट करने के लिए मूल्य प्राप्त करने की आवश्यकता है, तो आप की तरह कुछ कॉल कर सकते हैं! मुझे सीखना चाहिए कि विनिर्देशों को अंत से अंत तक कैसे पढ़ा जाए :-(

हालांकि, अब मैंने काम करने के बजाय चारों ओर बेवकूफ बनाकर बहुत अधिक समय बिताया है, फिर भी मैं उम्मीद करता हूं कि इससे लोगों को पढ़ने, प्रेरित करने के लिए प्रेरित किया जाएगा , समझें (महत्वपूर्ण) और फिर कार्य करें। या जेनेरिक, लैम्ब्डा और हास्यास्पद लिंक सामान के साथ कितना चालाक होना चाहिए।

एक स्वच्छ चाल मैं इस अभ्यास के दौरान की खोज की, उन निजी भीतरी वर्ग है जो Dictionary से निकला है। उनका पूरा उद्देश्य उन कोण ब्रैकेट को को पठनीयता में सुधार करने के लिए निकालना है।

ओह, लगभग भूल गया कोड:

अद्यतन: कोड बनाया सामान्य और क्या हर किसी को योगदान दिया है मैं के साथ आए हैं के आधार पर 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; } 
    } 
} 
+1

यह समाधान बहुत कम कोड है जो मुझे मिला है, केवल मुझे लगता है कि यह एक अनजान आवश्यकता का उल्लंघन करता है - मुझे क्रमबद्ध सूचियों के रूप में IQueryable देर से बाध्यकारी की आवश्यकता है, फिर बड़े SQL डेटा सेटों को छोड़ें/ले जाएं। हालांकि आपने मदद की - मुझे इस उद्देश्य के लिए "एडसोर्ट" पसंद नहीं आया, "परिभाषित" बहुत बेहतर है। – David

+0

हां, मैंने देखा कि आपने IQueryable इंटरफ़ेस का उपयोग किया था, लेकिन मुझे बहुत उत्साहित हो गया कि मैं इसके बारे में सब भूल गया। मैं देखूंगा कि क्या मैं इसे प्राप्त कर सकता हूं ... –

8

के बजाय IQueryable उपयोग करने के लिए निम्नलिखित।

यह द्वि-दिशात्मक सॉर्टिंग प्रदान करता है साथ ही समस्या को हल करने में भी मदद करता है। इसका मतलब यह है कि मुझे यह समझ में नहीं आया कि किसी दिए गए प्रकार की प्रत्येक अनुरक्षित सूची के लिए एक नया सॉर्टर बनाया जाना चाहिए। यह क्यों नहीं छोड़ा गया सूची सॉर्टर में पारित किया जा सकता है। यह तो मतलब है कि हम हमारे विभिन्न प्रकार के लिए सॉर्टर के 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 पर निर्भरता के लिए नहीं करना चाहता था यह wouldn बदलना मुश्किल नहीं है।

उम्मीद है कि यह किसी की मदद करेगा।

+4

आपको सॉर्ट इंस्ट्रक्शन को सॉर्ट इंस्ट्रक्शन – Timmerz

3

मुझे उपरोक्त काम पसंद आया - बहुत बहुत धन्यवाद! मैंने कुछ चीजों को जोड़ने की स्वतंत्रता ली:

  1. जोड़ा गया क्रम दिशा।

  2. पंजीकरण और दो अलग-अलग चिंताओं को बुलाया।

उपयोग:

var censusSorter = new Sorter<CensusEntryVM>(); 
censusSorter.AddSortExpression("SubscriberId", e=>e.SubscriberId); 
censusSorter.AddSortExpression("LastName", e => e.SubscriberId); 

View.CensusEntryDataSource = censusSorter.Sort(q.AsQueryable(), 
    new Tuple<string, SorterSortDirection>("SubscriberId", SorterSortDirection.Descending), 
    new Tuple<string, SorterSortDirection>("LastName", SorterSortDirection.Ascending)) 
    .ToList(); 



internal class Sorter<E> 
{ 
    public Sorter() 
    { 
    } 
    public void AddSortExpression<P>(string name, Expression<Func<E, P>> selector) 
    { 
     // Register all possible types of sorting for each parameter 
     firstPasses.Add(name, s => s.OrderBy(selector)); 
     nextPasses.Add(name, s => s.ThenBy(selector)); 
     firstPassesDesc.Add(name, s => s.OrderByDescending(selector)); 
     nextPassesDesc.Add(name, s => s.OrderByDescending(selector)); 
    } 

    public IOrderedQueryable<E> Sort(IQueryable<E> list, 
            params Tuple<string, SorterSortDirection>[] names) 
    { 
     IOrderedQueryable<E> result = null; 
     foreach (var entry in names) 
     { 
      result = result == null 
        ? SortFirst(entry.Item1, entry.Item2, list) 
        : SortNext(entry.Item1, entry.Item2, result); 
     } 
     return result; 
    } 
    private IOrderedQueryable<E> SortFirst(string name, SorterSortDirection direction, 
              IQueryable<E> source) 
    { 
     return direction == SorterSortDirection.Descending 
      ? firstPassesDesc[name].Invoke(source) 
      : firstPasses[name].Invoke(source); 
    } 

    private IOrderedQueryable<E> SortNext(string name, SorterSortDirection direction, 
              IOrderedQueryable<E> source) 
    { 
     return direction == SorterSortDirection.Descending 
      ? nextPassesDesc[name].Invoke(source) 
      : nextPasses[name].Invoke(source); 
    } 

    private readonly FirstPasses firstPasses = new FirstPasses(); 
    private readonly NextPasses nextPasses = new NextPasses(); 
    private readonly FirstPasses firstPassesDesc = new FirstPasses(); 
    private readonly NextPasses nextPassesDesc = new NextPasses(); 

    private class FirstPasses : Dictionary<string, Func<IQueryable<E>, IOrderedQueryable<E>>> { } 
    private class NextPasses : Dictionary<string, Func<IOrderedQueryable<E>, IOrderedQueryable<E>>> { } 
} 
+0

में बदलने की आवश्यकता है यह वास्तव में अच्छा है। मैं अनुमान लगा रहा हूं कि आपने AddSortExpression को डिसकप्ल किया है और सॉर्ट किया गया है ताकि आप अपने डेटा सेट में कुछ 'कॉलम' पर सॉर्टिंग अक्षम कर सकें? –

संबंधित मुद्दे