Vra

Ek wil graag die volgende kies verklaring dinamiese genereer met behulp van uitdrukking bome:

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

Ek het uitgewerk hoe om te genereer

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

maar ek kan nie lyk na 'n konstruktor / oorlading wat jou sal laat my spesifiseer verskeie eiendomme in my kies lambda vind.

Was dit nuttig?

Oplossing

Dit kan gedoen word, soos genoem, met die hulp van Refleksie uitstraal en 'n helper klas wat ek hieronder ingesluit. Die kode hieronder is 'n work in progress, so neem dit vir wat dit werd is ... "dit werk op my boks. Die SelectDynamic metode klas moet geteister in 'n statiese uitbreiding metode klas.

Soos verwag, sal jy enige IntelliSense nie aangesien die tipe nie geskep word totdat runtime. Werk goed op laat-gebonde data kontrole.

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

Ander wenke

Die aanvaarde antwoord is baie nuttig, maar ek nodig het iets 'n bietjie nader aan 'n ware anoniem tipe.

'n ware anoniem tipe het lees net eiendomme, 'n konstruktor vir die invul van al die waardes, 'n uitvoering van gelykes / GetHashCode vir die vergelyking van die waardes van elke eiendom, en 'n implementering toString wat die naam / waarde van elke eiendom sluit . (Sien https://msdn.microsoft.com/en-us/library/bb397696 Aspx vir 'n volledige beskrywing van anonieme tipes.)

Op grond van daardie omskrywing van anonieme klasse, sit ek 'n klas wat dinamiese anoniem tipes genereer op GitHub by https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs . Die projek bevat ook 'n paar eenheid toetse om seker te maak die valse anoniem tipes optree soos ware kinders.

Hier is 'n baie basiese voorbeeld van hoe om dit te gebruik:

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

Ook, 'n ander noot: Ek het gevind dat by die gebruik van 'n dinamiese anoniem tipe met Entiteit Raamwerk, moet die konstruktor genoem word met die "lede" parameter stel. Byvoorbeeld:

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

As jy gebruik een van die weergawes van Expression.New wat nie die parameter "lede" insluit nie, Entity Framework sou dit nie erken as die konstruktor van 'n anonieme tipe. So ek neem aan dit beteken 'n ware anoniem tipe se konstruktor uitdrukking wil hê dat "lede" inligting in te sluit.

Jy kan die hier IQueryable-uitbreidings, wat is 'n implemantation van die beskryf deur "Ethan J. Brown" oplossing te gebruik

https://github.com/thiscode/DynamicSelectExtensions

Die Uitbreiding bou dinamiese 'n anonieme tipe.

Dan kan jy dit doen:

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

Miskien 'n bietjie laat, maar kan help om iemand.

Jy kan dinamiese kies deur oproep DynamicSelectGenerator in kies uit 'n entiteit op te wek.

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();
        }

En gebruik deur hierdie kode:

var result = dbContextInstancs.EntityClass.Select(DynamicSelectGenerator<EntityClass>());

Ek glo nie dat jy in staat is om dit te bereik sal wees. Alhoewel as jy doen select new { c.Name, c.Population } dit lyk asof jy nie die skep van 'n klas wat jy eintlik is. As jy 'n blik op die saamgestel uitset in Reflector of die rou IL het jy sal in staat wees om dit te sien.

Jy sal 'n klas het wat so iets sou lyk:

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

(Ok, ek skoongemaak it up 'n aanraking, aangesien 'n eiendom is regtig net 'n get_Name() en set_Name(name) metode in elk geval stel)

Wat jy probeer doen is om behoorlike dinamiese klas skepping, iets wat gewoond beskikbaar wees totdat NET 4.0 kom uit (en selfs dan is ek nie seker of dit sal in staat wees om te bereik wat jy wil).

Jy is die beste oplossing sou wees om die verskillende anoniem klasse te definieer en dan het 'n soort van logiese tjek om te bepaal watter een om te skep, en om dit te skep wat jy kan die voorwerp System.Linq.Expressions.NewExpression gebruik.

Maar, kan dit wees (in teorie ten minste) moontlik om dit te doen, as jy kry regtig hard-core oor die onderliggende LINQ verskaffer. As jy is die skryf van jou eie LINQ verskaffer wat jy kan opspoor indien die oomblik-ontleed uitdrukking is 'n Gekose, dan bepaal jy die CompilerGenerated klas, weerspieël vir sy konstruktor en te skep.

Uitdagend nie 'n eenvoudige taak nie, maar dit sou wees hoe LINQ na SQL, LINQ na XML, ens al doen dit.

Jy kan gebruik 'n parameter klas in plaas van die werk met 'n anonieme tipe. In jou voorbeeld wat jy kan 'n parameter klas te skep soos volg:

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

... en sit dit in jou kies soos volg:

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

Wat jy uit is iets van die tipe IQueryable<ParamClass>.

Dit stel, ek weet nie of dit werk egter ...

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

Die aanvaarding van p is wat jou transformasie, en die kies stelling terugkeer 'n anon tipe, met behulp van die funksie verklaring van lambda se.

Edit: Ek weet ook nie hoe jy hierdie dinamiese sou genereer. Maar ten minste is dit wys jou hoe om die kies lambda gebruik om terug te keer 'n anon tipe met verskeie waardes

Edit2:

Jy sal ook moet gedra in gedagte, wat die c # samesteller eintlik genereer statiese klasse van die anon tipe. So die anon tipe nie eintlik 'n tipe na kompilering. So as jou genereer hierdie navrae te hardloop tyd (wat ek neem aan jy is) wat u mag hê om 'n tipe bou met behulp van die verskillende weerspieëling metodes (Ek glo jy kan dit gebruik om vorme te maak op die vlieg) laai die geskape tipes in uitvoering konteks en gebruik dit in jou gegenereer uitset.

Ek dink die meeste van die dinge is reeds beantwoord - as Slace gesê, moet jy 'n paar klas wat jou sal teruggestuur word van die Select metode. Sodra jy die klas, kan jy die System.Linq.Expressions.NewExpression metode gebruik om die uitdrukking te skep.

As jy regtig wil om dit te doen, kan jy die klas tydens looptyd te genereer. Dit is 'n bietjie meer werk nie, want dit kan nie gedoen word met behulp van LINQ Expression bome, maar dit is moontlik. Jy kan System.Reflection.Emit naamruimte gebruik om dit te doen - ek het net 'n vinnige soektog en hier is 'n artikel wat dit verduidelik:

Jy kan die dinamiese uitdrukking API wat jou toelaat om dinamiese bou jou kies stelling soos hierdie gebruik:

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

Jy moet die Dynamics.cs lêer van die LINQ en taal monsters vir Visual Studio vir hierdie om te werk, sowel gekoppel aan die onderkant van hierdie bladsy . Jy kan ook sien 'n werkende voorbeeld wat hierdie in aksie op te dieselfde adres.

Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top