2009-03-03 15 views
40

मैं गतिशील अभिव्यक्ति के पेड़ का उपयोग करके निम्न का चयन करें बयान उत्पन्न करने के लिए चाहते हैं का चयन करने के LINQ अभिव्यक्ति ट्री बनाने के लिए:कैसे एक गुमनाम प्रकार

var v = from c in Countries 
     where c.City == "London" 
     select new {c.Name, c.Population}; 

मैं बाहर काम किया है कैसे उत्पन्न करने के लिए

var v = from c in Countries 
     where c.City == "London" 
     select new {c.Name}; 

लेकिन मुझे एक कन्स्ट्रक्टर/अधिभार नहीं मिल रहा है जो मुझे मेरे चुनिंदा लैम्ब्डा में कई गुण निर्दिष्ट करने देगा।

Select("new(<property1>,<property2>,...)"); 

आप Dynamics.cs काम करने के लिए इस के लिए LINQ और दृश्य स्टूडियो के लिए भाषा का नमूनों से फाइल की जरूरत है:

+3

आप कोड है कि आप अब तक लेकर आए हैं पोस्ट कर सकते हैं? – Andy

उत्तर

62

यह किया जा सकता है के रूप में उल्लेख किया है, प्रतिबिंब की मदद से एमिट और एक सहायक वर्ग मैंने नीचे शामिल किया है। नीचे दिया गया कोड प्रगति पर एक काम है, इसलिए इसे इसके लायक बनाएं ... 'यह मेरे बॉक्स पर काम करता है'। चुनिंदा गतिशील विधि वर्ग को स्थिर विस्तार विधि वर्ग में फेंक दिया जाना चाहिए।

जैसा कि अपेक्षित है, आपको रनटाइम तक टाइप नहीं बनाया गया है, इसलिए आपको कोई इंटेलिजेंस नहीं मिलेगा। देर से डेटा नियंत्रण पर अच्छा काम करता है।

public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames) 
{ 
    Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name)); 
    Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values); 

    ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t"); 
    IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>(); 

    Expression selector = Expression.Lambda(Expression.MemberInit(
     Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem); 

    return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType }, 
       Expression.Constant(source), selector)); 
} 



public static class LinqRuntimeTypeBuilder 
{ 
    private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 
    private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" }; 
    private static ModuleBuilder moduleBuilder = null; 
    private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>(); 

    static LinqRuntimeTypeBuilder() 
    { 
     moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name); 
    } 

    private static string GetTypeKey(Dictionary<string, Type> fields) 
    { 
     //TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter 
     string key = string.Empty; 
     foreach (var field in fields) 
      key += field.Key + ";" + field.Value.Name + ";"; 

     return key; 
    } 

    public static Type GetDynamicType(Dictionary<string, Type> fields) 
    { 
     if (null == fields) 
      throw new ArgumentNullException("fields"); 
     if (0 == fields.Count) 
      throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition"); 

     try 
     { 
      Monitor.Enter(builtTypes); 
      string className = GetTypeKey(fields); 

      if (builtTypes.ContainsKey(className)) 
       return builtTypes[className]; 

      TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable); 

      foreach (var field in fields)      
       typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public); 

      builtTypes[className] = typeBuilder.CreateType(); 

      return builtTypes[className]; 
     } 
     catch (Exception ex) 
     { 
      log.Error(ex); 
     } 
     finally 
     { 
      Monitor.Exit(builtTypes); 
     } 

     return null; 
    } 


    private static string GetTypeKey(IEnumerable<PropertyInfo> fields) 
    { 
     return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType)); 
    } 

    public static Type GetDynamicType(IEnumerable<PropertyInfo> fields) 
    { 
     return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType)); 
    } 
} 
+0

अद्भुत, रनटाइम पर एक प्रकार का निर्माण नहीं करना इतना आसान था! धन्यवाद! – mCasamento

+0

अच्छा है लेकिन "इंटरफ़ेस सिस्टम को क्रमबद्ध नहीं कर सकता है। Linq.IQueryable" –

+2

आप ऑप्टिमाइज़ेशन के लिए अपने // TODO में ऑर्डरबी डाल सकते हैं और इसे पूरा कर सकते हैं। –

0

आप जो आप गतिशील रूप से इस तरह अपने चुनिंदा बयान बनाने की अनुमति देता गतिशील अभिव्यक्ति एपीआई इस्तेमाल कर सकते हैं , दोनों this page के नीचे जुड़े हुए हैं। आप एक यूआरएल पर कार्रवाई में यह दिखाते हुए एक कामकाजी उदाहरण भी देख सकते हैं।

+0

मेरा मानना ​​है कि केवल LINQ से SQL के साथ काम करेगा, अन्य LINQ प्रदाता नहीं, हालांकि –

+0

मेरा मानना ​​है कि ढांचा केवल IQueryable के साथ काम करता है, न कि Inumerable के साथ। –

+0

मैंने आपको आर कोड का प्रयास किया है जिससे यह डेटा दे रहा है कि डेटाकॉन्टेक्स्ट का उपयोग करके इकाई फ्रेमवर्क में उपरोक्त कोड को कैसे कार्यान्वित किया जाए? – Thulasiram

1

मुझे विश्वास नहीं है कि आप इसे प्राप्त करने में सक्षम होंगे। यद्यपि आप select new { c.Name, c.Population } करते हैं ऐसा लगता है कि आप वास्तव में एक कक्षा नहीं बना रहे हैं। यदि आप परावर्तक या कच्चे आईएल में संकलित आउटपुट पर एक नज़र डालते हैं तो आप इसे देख पाएंगे।

आप एक वर्ग है जो कुछ इस तरह दिखेगा होगा:

(के बाद से एक संपत्ति वास्तव में वैसे भी सेट सिर्फ एक get_Name() और set_Name(name) विधि है, ठीक है, मैं इसे एक स्पर्श साफ)

[CompilerGenerated] 
private class <>c__Class { 
    public string Name { get; set; } 
    public int Population { get; set; } 
} 

जो आप करने की कोशिश कर रहे हैं वह उचित गतिशील वर्ग निर्माण है, जो कुछ भी उपलब्ध नहीं होगा जब तक कि .NET 4.0 बाहर नहीं आ जाता है (और तब भी मुझे सच में यकीन नहीं है कि यह आपके इच्छित चीजों को प्राप्त करने में सक्षम होगा)।

आप सबसे अच्छा समाधान अलग गुमनाम वर्गों को परिभाषित करने के लिए और उसके बाद निर्धारित करने के लिए जो एक बनाने के लिए, और यह बनाने के लिए वस्तु System.Linq.Expressions.NewExpression उपयोग कर सकते हैं तार्किक जांच के कुछ प्रकार है किया जाएगा रहे हैं।

लेकिन, यदि आप अंतर्निहित LINQ प्रदाता के बारे में वास्तव में कठिन हो रहे हैं, तो यह संभवतः (सिद्धांत में कम से कम) संभव हो सकता है। यदि आप अपने स्वयं के LINQ प्रदाता लिख ​​रहे हैं तो आप यह पता लगा सकते हैं कि वर्तमान में पारदर्शी अभिव्यक्ति एक चयन है या नहीं, तो आप CompilerGenerated कक्षा निर्धारित करते हैं, जो इसके निर्माता के लिए प्रतिबिंबित करते हैं और बनाते हैं।

Defiantly एक साधारण काम नहीं है, लेकिन यह होगा कि LINQ से SQL, LINQ से XML, आदि सभी ऐसा करते हैं।

+0

अच्छा सारांश। दयालुता एक नया प्रकार उत्पन्न करने के लिए अभिव्यक्ति उत्पन्न करने का कोई तरीका नहीं है। हालांकि, मैं कल्पना कर सकता हूं कि कीड़े का एक बड़ा हिस्सा खुलता है। :) – Inferis

+0

मैं जांचूंगा कि System.Linq.Dynamic काम में एक्सटेंशन कैसे हैं, मुझे लगता है कि यह वर्ग ऐसा करने का एक तरीका होना चाहिए यदि यह वर्ग ऐसा कर सके। –

1

आप किसी अज्ञात प्रकार के साथ काम करने के बजाय पैरामीटर क्लास का उपयोग कर सकते हैं। अपने उदाहरण में आप इस तरह एक पैरामीटर वर्ग बना सकते हैं:

public struct ParamClass { 
    public string Name { get; set; }; 
    public int Population { get; set; }; 
} 

... और अपने चयन में इसे इस तरह से रख:

var v = from c in Countries 
     where c.City == "London" 
     select new ParamClass {c.Name, c.Population}; 

आप बाहर निकलना किस प्रकार IQueryable<ParamClass> के बारे में कुछ है।

1

यह संकलित करता है, मुझे पता है कि यह काम करता है हालांकि ...

myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; }); 

मान लिया जाये कि पी क्या आपके रूपांतरित होने वाले है, और चयन बयान एक anon प्रकार लौटा रहा है, लैम्ब्डा के के समारोह घोषणा का उपयोग कर।

संपादित करें: मुझे यह भी नहीं पता कि आप इस गतिशील रूप से कैसे उत्पन्न करेंगे। लेकिन कम से कम यह है आप कैसे चयन लैम्ब्डा उपयोग करने के लिए कई

मूल्यों

EDIT2 के साथ एक anon प्रकार वापस जाने के लिए पता चलता है:

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

1

मुझे लगता है कि ज्यादातर चीजें पहले ही उत्तर दे चुकी हैं - जैसा कि स्लेस ने कहा था, आपको कुछ कक्षा की आवश्यकता है जो Select विधि से वापस आ जाएगी। एक बार कक्षा होने के बाद, आप अभिव्यक्ति बनाने के लिए System.Linq.Expressions.NewExpression विधि का उपयोग कर सकते हैं।

यदि आप वास्तव में ऐसा करना चाहते हैं, तो आप रनटाइम पर कक्षा भी उत्पन्न कर सकते हैं। यह थोड़ा और काम है, क्योंकि यह LINQ अभिव्यक्ति पेड़ का उपयोग करके नहीं किया जा सकता है, लेकिन यह संभव है। आप ऐसा करने System.Reflection.Emit नाम स्थान का उपयोग कर सकते हैं - यहाँ मैं सिर्फ एक त्वरित खोज किया था और एक लेख है कि इस बताते है:

2

आप IQueryable-एक्सटेंशन यहां इस्तेमाल कर सकते हैं, जो समाधान "एतान जे ब्राउन" द्वारा वर्णित के implemantation है:

https://github.com/thiscode/DynamicSelectExtensions

एक्सटेंशन गतिशील एक गुमनाम प्रकार बनाता है।

तो फिर तुम यह कर सकते हैं:

var YourDynamicListOfFields = new List<string>(

    "field1", 
    "field2", 
    [...] 

) 
var query = query.SelectPartially(YourDynamicListOfFields); 
6

स्वीकार किए जाते हैं जवाब बहुत उपयोगी है, लेकिन मैं एक छोटे से एक असली गुमनाम प्रकार के करीब कुछ की जरूरत है।

एक वास्तविक अनाम प्रकार में केवल पढ़ने योग्य गुण हैं, सभी मूल्यों को भरने के लिए एक कन्स्ट्रक्टर, प्रत्येक संपत्ति के मूल्यों की तुलना करने के लिए बराबर/गेटहाशकोड का कार्यान्वयन, और कार्यान्वयन ToString जिसमें प्रत्येक का नाम/मान शामिल है संपत्ति। (अज्ञात प्रकारों के पूर्ण विवरण के लिए https://msdn.microsoft.com/en-us/library/bb397696.aspx देखें।)

अनाम कक्षाओं की उस परिभाषा के आधार पर, मैंने एक कक्षा डाली जो https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs पर जिथब पर गतिशील अनाम प्रकार उत्पन्न करती है। इस परियोजना में कुछ यूनिट परीक्षण भी शामिल हैं ताकि यह सुनिश्चित किया जा सके कि फर्जी अनाम प्रकार वास्तविक लोगों की तरह व्यवहार करते हैं।

AnonymousTypeUtils.CreateObject(new Dictionary<string, object> 
{ 
    { "a", 1 }, 
    { "b", 2 } 
}); 

इसके अलावा, एक और नोट:

इसका इस्तेमाल इस तरह के एक बहुत ही बुनियादी उदाहरण है मैंने पाया कि जब इकाई की रूपरेखा के साथ एक गतिशील गुमनाम प्रकार का उपयोग, निर्माता "सदस्यों" के साथ बुलाया जाना चाहिए पैरामीटर सेट उदाहरण के लिए:

Expression.New(
    constructor: anonymousType.GetConstructors().Single(), 
    arguments: propertyExpressions, 
    members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray() 
); 

आप Expression.New के संस्करण है कि "सदस्य" पैरामीटर शामिल नहीं है में से एक का उपयोग किया है, इकाई की रूपरेखा एक गुमनाम प्रकार के निर्माता के रूप में मान्यता नहीं होता। तो मुझे लगता है कि इसका मतलब है कि एक वास्तविक अज्ञात प्रकार की कन्स्ट्रक्टर अभिव्यक्ति में "सदस्य" जानकारी शामिल होगी।

0

शायद थोड़ा देर हो चुकी है लेकिन किसी की मदद कर सकती है।

आप किसी इकाई से चयन में DynamicSelectGenerator पर डायनामिक चयन उत्पन्न कर सकते हैं।

public static Func<T, T> DynamicSelectGenerator<T>() 
      { 
       // get Properties of the T 
       var fields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray(); 

      // input parameter "o" 
      var xParameter = Expression.Parameter(typeof(T), "o"); 

      // new statement "new Data()" 
      var xNew = Expression.New(typeof(T)); 

      // create initializers 
      var bindings = fields.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(); 
     } 

और इस कोड से उपयोग करें:

var result = dbContextInstancs.EntityClass.Select(DynamicSelectGenerator<EntityClass>()); 
संबंधित मुद्दे