문제

Expression Tree를 사용하여 다음 SELECT 문을 동적으로 생성하고 싶습니다.

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 Lambda에서 여러 속성을 지정할 수있는 생성자/과부하를 찾을 수 없습니다.

도움이 되었습니까?

해결책

언급 한 바와 같이, 반사 방출의 도움과 아래에 포함 된 도우미 클래스를 통해이를 수행 할 수 있습니다. 아래 코드는 진행중인 작업이므로 가치가있는 작업을 취하십시오 ... '내 상자에서 작동합니다'. SelectDynamic 메소드 클래스는 정적 확장 메소드 클래스에 버려야합니다.

예상대로, 유형이 런타임까지 생성되지 않기 때문에 지능이 없습니다. 늦은 데이터 컨트롤에서 잘 작동합니다.

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

다른 팁

허용 된 답변은 매우 유용하지만 실제 익명 유형에 조금 더 가까운 것이 필요했습니다.

실제 익명 유형에는 읽기 전용 속성, 모든 값을 채우기위한 생성자, 각 속성의 값을 비교하기위한 Equals/gethashcode의 구현 및 각 속성의 이름/값을 포함하는 구현 토스트가 있습니다. (보다 https://msdn.microsoft.com/en-us/library/bb397696.aspx 익명 유형에 대한 전체 설명.)

익명 클래스의 정의를 기반으로 Github에서 동적 익명 유형을 생성하는 클래스를 넣었습니다. https://github.com/dotlattice/latticeutils/blob/master/latticeutils/anonymoustypeutils.cs. 이 프로젝트에는 또한 가짜 익명 유형이 실제 유형처럼 행동 할 수 있도록 일부 단위 테스트가 포함되어 있습니다.

다음은 사용 방법에 대한 매우 기본적인 예입니다.

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

또한 또 다른 참고 사항 : Entity Framework와 함께 동적 익명 유형을 사용할 때 "멤버"매개 변수 세트와 함께 생성자를 호출해야한다는 것을 알았습니다. 예를 들어:

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

"멤버"매개 변수가 포함되어 있지 않은 표현식 버전 중 하나를 사용한 경우 엔티티 프레임 워크는 익명 유형의 생성자로 인식하지 못합니다. 따라서 실제 익명 유형의 생성자 표현식에는 해당 "멤버"정보가 포함된다는 것을 의미합니다.

여기에서 iqueryable-extensions를 사용할 수 있습니다. 여기에는 "Ethan J. Brown"이 설명한 솔루션의 구현입니다.

https://github.com/thiscode/dynamicselectextensions

확장자는 동적으로 익명 유형을 구축합니다.

그런 다음 다음을 수행 할 수 있습니다.

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

어쩌면 조금 늦었지만 누군가에게 도움이 될 수 있습니다.

통화별로 동적 선택을 생성 할 수 있습니다 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>());

나는 당신이 이것을 달성 할 수 있다고 믿지 않습니다. 당신이 할 때 select new { c.Name, c.Population } 실제로 수업을 만들지 않는 것 같습니다. 반사기 또는 원시 IL의 컴파일 된 출력을 살펴보면 이것을 볼 수 있습니다.

당신은 다음과 같은 것처럼 보일 수있는 수업을 가질 것입니다.

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

(좋아요, 재산이 실제로 get_Name() 그리고 set_Name(name) 어쨌든 메소드 세트)

당신이하려는 것은 적절한 동적 클래스 생성입니다. .NET 4.0이 나올 때까지 사용할 수없는 것입니다 (심지어 원하는 것을 달성 할 수 있을지 확실하지 않습니다).

당신은 최상의 솔루션은 다른 것을 정의하는 것입니다 익명의 클래스를 한 다음 어떤 종류의 논리적 검사를 만들어 어떤 것을 만들 었는지 결정하고 그것을 만들려면 객체를 사용할 수 있습니다. System.Linq.Expressions.NewExpression.

그러나 기본 LINQ 제공 업체에 대해 실제로 하드 코어를 받고 있다면 (적어도 이론적으로) 가능할 수 있습니다. 만약 너라면 ~이다 자신만의 LINQ 제공 업체 작성 현재 수정 된 표현식이 선택된 경우 감지 할 수 있습니다. CompilerGenerated 클래스, 생성자를 반영하고 작성하십시오.

단순한 작업은 아니지만 LINQ에서 SQL, LINQ에서 XML 등이 모두 수행하는 방식입니다.

익명 유형으로 작업하는 대신 매개 변수 클래스를 사용할 수 있습니다. 예에서는 다음과 같은 매개 변수 클래스를 만들 수 있습니다.

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

이것은 컴파일합니다. 그러나 작동한다면 ...

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

p가 당신의 변환이고, select 문이 Lambda의 함수 선언을 사용하여 anon 유형을 반환한다고 가정합니다.

편집 : 나는 또한 당신이 어떻게 동적으로 생성 할 것인지 모르겠습니다. 그러나 적어도 일부 Lambda를 사용하여 여러 값의 Anon 유형을 반환하는 방법을 보여줍니다.

edit2 :

C# 컴파일러가 실제로 ANON 유형의 정적 클래스를 생성한다는 것을 명심해야합니다. 따라서 Anon 유형은 실제로 컴파일 시간 후에 유형이 있습니다. 따라서 런타임 에이 쿼리를 생성하는 경우 (내가 생각한다고 가정) 다양한 반사 방법을 사용하여 유형을 구성해야 할 수도 있습니다 (즉시 유형을 사용하여 유형을 만들 수 있다고 생각합니다). 생성 된 출력에 사용하십시오.

Slace가 말했듯이 대부분의 것들이 이미 대답되었다고 생각합니다. Select 방법. 수업이 있으면 System.Linq.Expressions.NewExpression 표현식을 생성하는 방법.

정말로이 작업을 원한다면 런타임에도 클래스를 생성 할 수 있습니다. LINQ 발현 나무를 사용하여 수행 할 수 없기 때문에 조금 더 많은 작업입니다. 당신이 사용할 수있는 System.Reflection.Emit 이를위한 네임 스페이스 - 방금 빠른 검색을했는데 여기에 설명하는 기사가 있습니다.

동적 표현식 API를 사용할 수 있으며, 이는 다음과 같은 선택 문을 동적으로 빌드 할 수 있습니다.

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

LINQ의 Dynamics.cs 파일과 Visual Studio 용 언어 샘플이 필요합니다. 이 페이지. 동일한 URL 에서이 작업을 보여주는 작업 예제를 볼 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top