【问题标题】:Retrieve attributes of properties after projection投影后检索属性的属性
【发布时间】:2018-02-07 09:19:10
【问题描述】:

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

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

和下面的代码sn-p。

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

但我愿意接受建议。

【问题讨论】:

  • 这是针对任何属性,还是仅针对特定属性
  • 我有一组有限的我感兴趣的属性。因此,如果您只能为 CustomAttribute1 执行此操作,我会很好。
  • 必须是匿名类型吗?匿名类型不允许有属性。如果您能够定义一个类,则可以复制属性。 stackoverflow.com/questions/1217437/…
  • 我也可以定义一个目标类型。实际上,这就是我现在的做法,并直接在该类型上声明自定义属性。但这将变得无法维护。
  • 您愿意为实体添加额外的int 字段吗?要指定如何映射发生?

标签: c# reflection attributes


【解决方案1】:

这样做的主要问题是源属性和目标属性之间没有关系。它们唯一的关系是选择中的赋值。更复杂的是,源类型和目标类型之间的关系可以根据 select 表达式进行更改,因此任何映射都必须针对每个 select 表达式。

建议的解决方案

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

创建Select 表达式时,将在表达式中搜索属性映射并记录下来。该映射将添加一个映射的全局列表及其在选择中使用的索引,如果多次使用相同的映射,则使用相同的索引。

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

  1. 使用GenerateMapId 方法显式设置字段。方法调用将替换为表示地图索引的常量。
  2. 实现IWithMetadataMapping 接口。在这种情况下,MapId 属性和表示地图索引的常量之间的绑定将被添加到 Select 表达式中

用法

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

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

【讨论】:

  • 它似乎有效,我还需要几个小时才能理解...谢谢!
猜你喜欢
  • 1970-01-01
  • 2017-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-01
  • 2017-04-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多