考虑以下示例。它正在使用实体框架,但我认为在这种情况下这并不重要。
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;
}
}
但我愿意接受建议。
答案 0 :(得分:0)
这个问题的主要问题是source属性和target属性之间没有关系。他们唯一的关系是select中的赋值。为了使事情进一步复杂化,源和目标类型之间的关系可以根据select表达式进行更改,因此任何映射都必须是每个select表达式。
建议的解决方案
将字段添加到任何结果类型,该类型为属性的映射指定if。该字段将是一个简单的int
常量,它将添加到select中,该常量将用于索引到列表以查找适当的映射。
创建Select
表达式时,将搜索表达式以查找属性映射,并记录这些表达式。地图将添加一个全局映射列表及其在select中使用的索引,如果多次使用相同的映射,则使用相同的索引。
表示映射的字段可以通过两种方式初始化:
GenerateMapId
方法明确设置字段。方法调用将替换为表示映射索引的常量。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);
}
}
}
注意我没有对此进行过广泛的测试,因此可能存在错误,如果您想使用该解决方案,并且遇到问题我可以帮助修复它们。