投影后检索属性的属性

时间:2018-02-07 09:19:10

标签: c# reflection attributes

考虑以下示例。它正在使用实体框架,但我认为在这种情况下这并不重要。

    class Foo
    {
        [CustomAttribute1(Value = "SomeValue")]
        public string Prop { get; set; }

        public Bar Bar { get; set; }
    }

    class Bar
    {
        [CustomAttribute1(Value = "OtherValue")]
        public string Prop { get; set; }
    }

    class CustomAttribute1Attribute : Attribute
    {
        public string Value { get; set; }
    }

以及以下代码段。

    var expr = from f in ctx.Foos
            select new
            {
                Prop1 = f.Prop,
                Prop2 = f.Bar.Prop
            };

    foreach (var obj in expr)
    {
       var attr1 =  // retrieve "SomeValue" from obj.Prop1
       var attr2 =  // retrieve "OtherValue" from obj.Prop2
    }

我希望能够以某种方式获得这些价值观。我愿意在expr上做一些额外的行动。我在想像

        var list = WithAttributeProjection(ctx => 
            from f in ctx.Foos
            select new
            {
                Prop1 = f.Prop,
                Prop2 = f.Bar.Prop
            });

        foreach (var obj in list)
        {
            var attr1 =   // retrieve "SomeValue" from obj.Prop1
            var attr2 =   // retrieve "OtherValue" from obj.Prop2
        }

        private IEnumerable WithAttributeProjection<T>(Expression<Func<Ctx, T>> expr)
        {                  
           // retrieve destination properties from expr
           // retrieve member expressions               
           // retrieve types from expr
           // retrieve attributes from types
           // create attributes on T [like this][1]   
           foreach (var obj in expr.Compile())
           {
               var result = new T();
               //copy obj to result
               yield return result;
           }                         
        }

但我愿意接受建议。

1 个答案:

答案 0 :(得分:0)

这个问题的主要问题是source属性和target属性之间没有关系。他们唯一的关系是select中的赋值。为了使事情进一步复杂化,源和目标类型之间的关系可以根据select表达式进行更改,因此任何映射都必须是每个select表达式。

建议的解决方案

将字段添加到任何结果类型,该类型为属性的映射指定if。该字段将是一个简单的int常量,它将添加到select中,该常量将用于索引到列表以查找适当的映射。

创建Select表达式时,将搜索表达式以查找属性映射,并记录这些表达式。地图将添加一个全局映射列表及其在select中使用的索引,如果多次使用相同的映射,则使用相同的索引。

表示映射的字段可以通过两种方式初始化:

  1. 使用GenerateMapId方法明确设置字段。方法调用将替换为表示映射索引的常量。
  2. 实施IWithMetadataMapping界面。在这种情况下,MapId属性和表示地图索引的常量之间的绑定将添加到Select表达式
  3. <强>用法

    var result = c.Entities
        .WithAttributeProjection(e => new
        {
            MapId = Extensions.GenerateMapId(), // We need mapping for this type
            Prop1 = e.Id,
            Prop2 = e.Name,
            Prop3 = e.Child.Name,
            Nested = new
            {
                MapId = Extensions.GenerateMapId(), // Also for this type
                Prop1 = e.Id,
                Prop2 = e.Name,
                Prop3 = e.Child.Name,
                Children = e.ManyChild.Select(m => new WithMetadataMapping // This implements IWithMetadataMapping so it will get the mappgin automatically 
                {
                    Name = m.Name
                })
            }
        });
    
    foreach (var item in result)
    {
        var map = Extensions.GetMapping(item.MapId); // Get the map for the outer new 
    
        var type = item.GetType(); 
        var prop2 = type.GetProperty(nameof(item.Prop2));
        var sourceProp2 = map.GetSourceMember(prop2);
        var value2 = sourceProp2.GetCustomAttribute<CustomAttribute1Attribute>().Value;
        Console.WriteLine(value2); // Entity.Name
    
        var prop3 = type.GetProperty(nameof(item.Prop3));
        var sourceProp3 = map.GetSourceMember(prop3);
        var value3 = sourceProp3.GetCustomAttribute<CustomAttribute1Attribute>().Value;
        Console.WriteLine(value3); // ChildEntity.Name
    
        foreach (var child in item.Nested.Children)
        {
            var childMap = Extensions.GetMapping(child.MapId);
            var nameProp = child.GetType().GetProperty(nameof(child.Name));
            var value = childMap.GetSourceMember(nameProp).GetCustomAttribute<CustomAttribute1Attribute>().Value;
            Console.WriteLine(value); // ManyChildEntity.Name
        }
    
    }
    

    <强>实施

    interface IWithMetadataMapping
    {
        int MapId { get; set; }
    }
    public static class Extensions 
    {
        private static MethodInfo GenerateMapIdMethodInfo = typeof(Extensions).GetMethod(nameof(GenerateMapId));
        public static int GenerateMapId()
        {
            throw new NotSupportedException("Should not be invoked directly");
        }
        static Dictionary<Mapping, int> MappingLookup = new Dictionary<Mapping, int>();
        static List<Mapping> Mappings = new List<Mapping>();
    
        public static Mapping GetMapping(int index)
        {
            return Mappings[index];
        }
        public class Mapping
        {
            public Mapping(Dictionary<MemberInfo, MemberInfo[]> propertyMappings)
            {
                this.propertyMappings = propertyMappings;
                int hc = propertyMappings.Count;
                foreach (var kv in propertyMappings)
                {
                    hc = unchecked(hc * 314159 + kv.Key.GetHashCode());
                    hc = unchecked(hc * 314159 + kv.Value.Length);
                    foreach (var pi in kv.Value)
                    {
                        hc = unchecked(hc * 314159 + pi.GetHashCode());
                    }
                }
    
                this.hashCode = hc;
            }
            private Dictionary<MemberInfo, MemberInfo[]> propertyMappings;
            private int hashCode;
            public override int GetHashCode() => this.hashCode;
    
            public override bool Equals(object obj)
            {
                if(obj is Mapping map)
                {
                    return map.propertyMappings.Count == this.propertyMappings.Count &&
                        map.propertyMappings.Keys.SequenceEqual(this.propertyMappings.Keys) &&
                        map.propertyMappings.Values
                            .Zip(this.propertyMappings.Values, (a, b) => a.SequenceEqual(b))
                            .All(v => v);
                }
    
                return false;
            }
    
            public MemberInfo[] GetSourceMemberChain(MemberInfo pi)
            {
                return this.propertyMappings[pi];
            }
    
            public bool TryGetSourceMemberChain(MemberInfo pi, out MemberInfo[] source)
            {
                return this.propertyMappings.TryGetValue(pi, out source) ;
            }
    
            public MemberInfo GetSourceMember(MemberInfo pi)
            {
                return this.GetSourceMemberChain(pi).First();
            }
    
            public bool TryGetSourceMember(MemberInfo pi, out MemberInfo source)
            {
                if(this.propertyMappings.TryGetValue(pi, out var sources))
                {
                    source = sources.First();
                    return true;
                }
                else
                {
                    source = null;
                    return false;
                }
            }
        }
        public static IQueryable<TResult> WithAttributeProjection<TSource, TResult>(this IQueryable<TSource> @this, Expression<Func<TSource, TResult>> selector)
        {
            var visitor = new MapExtractionVisitor();
    
            var newExpression = (Expression<Func<TSource, TResult>>)visitor.Visit(selector);
    
            return @this.Select(newExpression);
        }
    
        public class MapExtractionVisitor : ExpressionVisitor
        {
            List<MemberInfo> capturedMemebers;
            bool capturePropAccess = false;
    
            protected void CreateMapping(int count, 
                Func<int, Expression> getValue,
                Action<int, Expression> setValue,
                Func<int, MemberInfo> getMemeber,
                Func<int, bool> isGenerateMapId,
                bool alwaysExtractMap,
                out bool isDirty)
            {
                var mappingIdMember = -1;
                isDirty = false;
                Dictionary<MemberInfo, MemberInfo[]> propertyMappings = new Dictionary<MemberInfo, MemberInfo[]>();
    
                for (int index = 0; index < count; index++)
                {
    
                    if (isGenerateMapId(index))
                    {
                        mappingIdMember = index;
                    }
    
                    var arg = getValue(index);
                    if (arg == null) continue;
    
    
                    var originalCapturePropAccess = this.capturePropAccess;
                    var originalCapturedMemebers = this.capturedMemebers;
                    this.capturePropAccess = true;
                    this.capturedMemebers = new List<MemberInfo>();
                    var newArgument = this.Visit(arg);
    
                    setValue(index, newArgument);
                    isDirty = isDirty || newArgument != arg;
                    if (this.capturedMemebers.Any())
                    {
                        propertyMappings.Add(getMemeber(index), this.capturedMemebers.ToArray());
                    }
    
                    this.capturedMemebers = originalCapturedMemebers;
                    this.capturePropAccess = originalCapturePropAccess;
                }
    
                if (mappingIdMember != -1 || alwaysExtractMap)
                {
                    lock (MappingLookup)
                    {
                        var newMapping = new Mapping(propertyMappings);
                        if (!MappingLookup.TryGetValue(newMapping, out var mapId))
                        {
                            mapId = Mappings.Count;
                            Mappings.Add(newMapping);
                            MappingLookup.Add(newMapping, mapId);
                        }
                        setValue(mappingIdMember, Expression.Constant(mapId));
                    }
                    isDirty = true;
                }
            }
            protected override Expression VisitNew(NewExpression node)
            {
                if (node.Members != null)
                {
                    Expression[] newArguments = new Expression[node.Arguments.Count];
                    CreateMapping(node.Arguments.Count, 
                        i => node.Arguments[i], 
                        (i, e) => newArguments[i] = e, 
                        i => node.Members[i],
                        i => node.Arguments[i] is MethodCallExpression mArg && mArg.Method == GenerateMapIdMethodInfo,
                        false,
                        out var isDirty);
    
                    if (isDirty)
                    {
                        return node.Update(newArguments);
                    }
                    else
                    {
                        return node;
                    }
                }
                else
                {
                    return base.VisitNew(node);
                }
            }
    
            protected override Expression VisitMemberInit(MemberInitExpression node)
            {
                Expression[] newArguments = new Expression[node.Bindings.Count + 1];
                var bindings = node.Bindings;
    
                var implementsInterface = typeof(IWithMetadataMapping).IsAssignableFrom(node.Type);
    
                CreateMapping(bindings.Count,
                     i => bindings[i] is MemberAssignment ma ? ma.Expression : null,
                     (i, e) => newArguments[i != -1 ? i : (newArguments.Length - 1)] = e,
                     i => bindings[i] is MemberAssignment ma ? ma.Member : null,
                     i =>
                     {
                         if (bindings[i] is MemberAssignment ma)
                         {
                             if (ma.Expression is MethodCallExpression mArg && mArg.Method == GenerateMapIdMethodInfo)
                             {
                                 return true;
                             }
                         }
                         return false;
                     },
                     implementsInterface,
                     out var isDirty);
    
                if (isDirty)
                {
                    int count = node.Bindings.Count;
                    bool addBinding = newArguments.Last() != null;
                    var inits = new MemberBinding[count + (addBinding ? 1 : 0)];
                    for (int i = 0; i < count; i++)
                    {
                        var binding = node.Bindings[i];
                        if (newArguments[i] == null)
                        {
                            inits[i] = binding;
                        }
                        else if(binding is MemberAssignment m)
                        {
                            inits[i] = Expression.Bind(m.Member, newArguments[i]);
                        }
                    }
                    if(addBinding)
                    {
                        var mapProp = node.Type.GetProperty(nameof(IWithMetadataMapping.MapId));
                        inits[inits.Length - 1] = Expression.Bind(mapProp, newArguments.Last());
                    }
                    return node.Update(node.NewExpression, inits);
                }
    
                return base.VisitMemberInit(node);
            }
    
            public override Expression Visit(Expression node)
            {
                // We only capture an interupted chanin of member accesses
                if(capturePropAccess && node is MemberExpression memberExpression)
                {
                    capturedMemebers.Add(memberExpression.Member);
                }
                else
                {
                    capturePropAccess = false;
                }
                return base.Visit(node);
            }
        }
    }
    

    注意我没有对此进行过广泛的测试,因此可能存在错误,如果您想使用该解决方案,并且遇到问题我可以帮助修复它们。