2017-02-23 1 views
1

Dies ist nur Auswahl von Spalten von Autos:Datacontext Übersetzen <> für anonyme Typen

var qs = myDataContext.Cars 
    .Select(c => new { c.id, c.color }) 
    .ToList(); 

Was ich brauche, ist eine Funktion, die das gleiche tun würde, aber über SqlCommand, so kann ich den Prozess verändern. Sein (vereinfacht) Code ist hier

public static IEnumerable<P> ProjectionFunction<T, P>(
    this System.Data.Linq.Table<T> input, 
    Func<T, P> projection 
    ) where T : class 
{ 
    System.Data.Linq.DataContext dc = input.Context; 

    string paramList = string.Join(
     ",", 
     typeof(P).GetProperties().Select(s => s.Name) 
     ); 

    System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(
     "SELECT " + paramList + " FROM " + typeof(T).Name, 
     (System.Data.SqlClient.SqlConnection)dc.Connection 
     ); 

    cmd.CommandType = CommandType.Text; 

    if (cmd.Connection.State == ConnectionState.Closed) 
    { 
     cmd.Connection.Open(); 
    } 

    return dc.Translate<P>(cmd.ExecuteReader()); // <-- the problem is here 
} 

Die Funktion wie diese auch für den normalen Klassen arbeitet

private class X 
{ 
    public int? id { get; set; } 
    public string color { get; set; } 
} 

var qx = myDataContext.Cars 
    .ProjectionFunction(c => new X() { id = c.id, color = c.color }) 
    .ToList(); 

aber es funktioniert nicht auf anonyme Typen

var qa = myDataContext.Cars 
    .ProjectionFunction(c => new { c.id, c.color }) 
    .ToList(); 

ich Laufzeitfehler

Der Typ <> f__AnonymousType20`2 [System.Nullable`1 [System.Int32], System.String] muss einen (parameterlosen) Standardkonstruktor deklarieren, damit beim Mapping erstellt wird.

für Translate<> Funktion. Die ExecuteQuery<> versuchte ich auch gleich. Es ist schwer zu glauben, dass DataContext nicht weiß, wie man einen anonymen Typ erstellt, das ist, was er die ganze Zeit macht. Was vermisse ich? Wie kann ich ihn dazu bringen, das für mich zu tun?

Das Problem mit der separaten Klasse für eine Verwendung ist, dass ihre Eigenschaften explizit mit Typen und Namen von Eigenschaften der ursprünglichen Klasse synchronisiert werden müssen, was diesen Ansatz etwas unpraktisch macht.

Antwort

0

Ich möchte immer noch wissen, ob es eine Möglichkeit gibt, DataContext in anonyme Typen zu übersetzen, aber es kann gut sein, dass es dieses Verhalten nicht aussetzt. In diesem Fall muss ein anderer Materialisierer verwendet werden. Am Ende war es gar nicht so schwer, einen zu schreiben, also habe ich es geteilt, für den Fall, dass jemand Interesse hat. Der anonyme Typ kann nur über einen neuen Operator initialisiert werden. Glücklicherweise kann der Ausdruck dafür in Laufzeit erstellt werden.

private static Func<object[], P> getMaterializer<P>(
    System.Reflection.PropertyInfo[] props, IEnumerable<string> propertyNames) 
{ 
    Type[] propertyTypes = props.Select(p => p.PropertyType).ToArray(); 
    ParameterExpression arrExpr = Expression.Parameter(typeof(object[])); 
    var constructor = typeof(P).GetConstructor(propertyTypes); 

    if (constructor == null || !constructor 
     .GetParameters() 
     .Select(p => p.Name) 
     .SequenceEqual(propertyNames)) 
    { 
     return null; 
    } 

    Expression[] paramExprList = propertyTypes.Select((type, i) => 
    { 
     Expression ei = Expression.ArrayIndex(arrExpr, Expression.Constant(i)); 

     if (type.IsGenericType || type == typeof(string)) 
     { 
      return (Expression)Expression.Condition(
       Expression.Equal(ei, Expression.Constant(DBNull.Value)), 
       Expression.Convert(Expression.Constant(null), type), 
       Expression.Convert(ei, type) 
       ); 
     } 
     else 
     { 
      return Expression.Convert(ei, type); 
     } 
    }).ToArray(); 

    return Expression.Lambda<Func<object[], P>>(
     Expression.New(constructor, paramExprList), 
     arrExpr 
     ).Compile(); 
} 


private static System.Collections.Concurrent.ConcurrentDictionary<Type, Tuple<string, string, object>> cachedProjections = 
    new System.Collections.Concurrent.ConcurrentDictionary<Type, Tuple<string, string, object>>(); 

private static Tuple<string, string, object> getProjection<T, P>() 
{ 
    Type typeP = typeof(P); 
    Tuple<string, string, object> projection; 

    if (!cachedProjections.TryGetValue(typeP, out projection)) 
    { 
     Type typeT = typeof(T); 
     System.Reflection.PropertyInfo[] props = typeP.GetProperties(); 
     List<string> propertyNames = props.Select(p => p.Name).ToList(); 

     projection = new Tuple<string, string, object>(
      string.Join(",", propertyNames), 
      typeT.Name, 
      typeT == typeP ? null : getMaterializer<P>(props, propertyNames) 
      ); 

     cachedProjections.TryAdd(typeP, projection); 
    } 

    return projection; 
} 

private static IEnumerable<P> Materialize<P>(SqlCommand cmd, 
    Func<object[], P> materializer) 
{ 
    using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) 
    { 
     object[] obj = new object[reader.VisibleFieldCount]; 

     while (reader.Read()) 
     { 
      reader.GetValues(obj); 
      yield return materializer(obj); 
     } 
    } 
} 

public static IEnumerable<P> ProjectionFunction<T, P>(
    this System.Data.Linq.Table<T> input, 
    Func<T, P> projectionFunction 
) where T : class 
{ 
    var projection = getProjection<T, P>(); 

    using (SqlCommand cmd = new SqlCommand(
     "SELECT " + projection.Item1 
     + " FROM " + projection.Item2, 
     new SqlConnection(input.Context.Connection.ConnectionString) 
     )) 
    { 
     cmd.CommandType = CommandType.Text; 
     cmd.Connection.Open(); 

     var materializer = (Func<object[], P>)projection.Item3; 
     if (materializer == null) 
     { 
      return input.Context.Translate<P>(cmd.ExecuteReader(CommandBehavior.CloseConnection)); 
     } 
     else 
     { 
      return Materialize(cmd, materializer); 
     } 
    } 
} 
Verwandte Themen