我在尝试获取“内部图”成员的有效表达式时遇到麻烦。
我根据先前的堆栈溢出答案写了几行代码。但是我在寻找一种好的“解决方案”时遇到了问题。
例如,尝试将“字符串”表示为lambda字段成员...
我已经重构了ToMemberOf扩展方法,但对于“内部图”成员而言确实如此,但它会失败。
即仅对“直接成员”有效。
var order1 = "FullName".ToMemberOf<Player>(); // will be converted to {e => Convert(e.FullName, Object)}
但适用于“内部图表”成员,即
var order2 = "Catalog.Photo.FileName".ToMemberOf<Player>()
将失败,并出现System.ArgumentException,因为ToMemberOf扩展方法无法处理“点”和“内部图”成员。
然后我尝试编写一个名为ToExtendedMemberOf的新扩展方法
它似乎可以工作,但是在使用EF Core时失败。
即“内部图”成员“ Catalog.Photo.FileName”将被转换为。
var order2 = "Catalog.Photo.FileName".ToExtendedMemberOf<Player>(); // will be converted to {e => new Player() {Catalog = new Catalog() {Photo = new Photo() {FileName = e.Catalog.Photo.FileName}}}}
但是当我尝试将其与EF内核一起使用时,出现了提供商特定的错误。
即对于InMemory提供程序,我得到了System.InvalidOperationException:无法比较数组中的两个元素
即对于SlqServer提供程序,我得到了System.InvalidOperationException LINQ表达式无法转换
实际上,我希望在服务器上而不是在客户端上执行评估。
我不知道如何重构ToExtendedMemberOf或ToMemberOf以实现这些目标。我需要您的帮助
我正在粘贴整个单元文本示例,以实现更广阔的视野。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Xunit;
namespace XUnitTestProject
{
public static class QueryableExtensions
{
public static IQueryable<T> Select<T>(this IQueryable queryable, IEnumerable<string> fields) where T : class
{
var sourceType = queryable.ElementType;
var resultType = typeof(T);
var parameter = Expression.Parameter(sourceType, "e");
var body = GetNewMember(typeof(T), parameter, fields.Select(f => f.Split('.')));
var selector = Expression.Lambda(body, parameter);
return queryable.Provider.CreateQuery<T>(Expression.Call(typeof(Queryable), "Select", new[] { sourceType, resultType }, queryable.Expression, Expression.Quote(selector)));
}
public static IQueryable<T> OrderBy<T>(this IQueryable queryable, IEnumerable<string> fields) where T : class
{
var sourceType = queryable.ElementType;
var resultType = typeof(T);
var parameter = Expression.Parameter(sourceType, "e");
var body = GetNewMember(typeof(T), parameter, fields.Select(f => f.Split('.')));
var selector = Expression.Lambda(body, parameter);
return queryable.Provider.CreateQuery<T>(Expression.Call(typeof(Queryable), "OrderBy", new[] { sourceType, resultType }, queryable.Expression, Expression.Quote(selector))); // {e => new Player() {Catalog = new Catalog() {Photo = new Photo() {FileName = e.Catalog.Photo.FileName}}}}
}
private static Expression GetNewMember(Type targetType, Expression source, IEnumerable<string[]> memberPaths, int depth = 0)
{
var target = Expression.Constant(null, targetType);
var bindings = memberPaths.GroupBy(path => path[depth]).Select(memberGroup =>
{
var memberName = memberGroup.Key;
var targetMember = Expression.PropertyOrField(target, memberName);
var sourceMember = Expression.PropertyOrField(source, memberName);
var childMembers = memberGroup.Where(path => depth + 1 < path.Length);
var enumerable = childMembers as string[][] ?? childMembers.ToArray();
var targetValue = !enumerable.Any() ? sourceMember : GetNewMember(targetMember.Type, sourceMember, enumerable, depth + 1);
return Expression.Bind(targetMember.Member, targetValue);
});
return Expression.MemberInit(Expression.New(targetType), bindings);
}
}
public static class StringExtensions
{
public static Expression<Func<T, object>> ToMemberOf<T>(this string name) where T : class
{
var parameter = Expression.Parameter(typeof(T), "e");
var propertyOrField = Expression.PropertyOrField(parameter, name);
var unaryExpression = Expression.MakeUnary(ExpressionType.Convert, propertyOrField, typeof(object));
return Expression.Lambda<Func<T, object>>(unaryExpression, parameter);
}
public static UnaryExpression ToExtendedMemberOf<T>(this string name) where T : class
{
var parameter = Expression.Parameter(typeof(T), "e");
var body = GetNewExtendedMember(typeof(T), parameter, new[] { name.Split('.').ToArray() });
var selector = Expression.Lambda(body, parameter);
return Expression.Quote(selector);
}
private static Expression GetNewExtendedMember(Type targetType, Expression source, IEnumerable<string[]> memberPaths, int depth = 0)
{
var target = Expression.Constant(null, targetType);
var bindings = memberPaths.GroupBy(path => path[depth]).Select(memberGroup =>
{
var memberName = memberGroup.Key;
var targetMember = Expression.PropertyOrField(target, memberName);
var sourceMember = Expression.PropertyOrField(source, memberName);
var childMembers = memberGroup.Where(path => depth + 1 < path.Length);
var enumerable = childMembers as string[][] ?? childMembers.ToArray();
var targetValue = !enumerable.Any() ? sourceMember : GetNewExtendedMember(targetMember.Type, sourceMember, enumerable, depth + 1);
return Expression.Bind(targetMember.Member, targetValue);
});
return Expression.MemberInit(Expression.New(targetType), bindings);
}
}
public class DataContext : DbContext
{
public DbSet<Player> Players { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
base.OnConfiguring(builder);
if (!builder.IsConfigured)
{
builder.UseInMemoryDatabase(Guid.NewGuid().ToString());
builder.ConfigureWarnings(w => w.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
}
}
public class Player
{
public int Id { get; set; }
public string FullName { get; set; }
public int Age { get; set; }
public Catalog Catalog { get; set; }
}
public class Photo
{
public int Id { get; set; }
public string FileName { get; set; }
public string Format { get; set; }
}
public class Catalog
{
public int Id { get; set; }
public string CatalogName { get; set; }
public string Color { get; set; }
public Photo Photo { get; set; }
}
public class UnitTest
{
[Fact]
public void Test()
{
var players = new[]
{
new Player
{
Id = 1,
FullName = "FullName 01",
Age = 1,
Catalog = new Catalog
{
Id = 1,
CatalogName = "CatalogName 01",
Color = "Color 01",
Photo = new Photo {Id = 1, FileName = "FileName 01", Format = "Format 01"}
}
},
new Player
{
Id = 2,
FullName = "FullName 02",
Age = 2,
Catalog = new Catalog
{
Id = 1,
CatalogName = "CatalogName 02",
Color = "Color 02",
Photo = new Photo {Id = 1, FileName = "FileName 02", Format = "Format 02"}
}
},
new Player
{
Id = 3,
FullName = "FullName 03",
Age = 3,
Catalog = new Catalog
{
Id = 1,
CatalogName = "CatalogName 03",
Color = "Color 03",
Photo = new Photo {Id = 1, FileName = "FileName 03", Format = "Format 03"}
}
},
new Player
{
Id = 4,
FullName = "FullName 04",
Age = 4,
Catalog = new Catalog
{
Id = 1,
CatalogName = "CatalogName 04",
Color = "Color 04",
Photo = new Photo {Id = 1, FileName = "FileName 04", Format = "Format 04"}
}
},
new Player
{
Id = 5,
FullName = "FullName 05",
Age = 5,
Catalog = new Catalog
{
Id = 1,
CatalogName = "CatalogName 05",
Color = "Color 05",
Photo = new Photo {Id = 1, FileName = "FileName 05", Format = "Format 05"}
}
},
};
using (var context = new DataContext())
{
context.Players.AddRange(players);
context.SaveChanges();
var queryable = context.Players as IQueryable<Player>;
var result1 = queryable
.Select(p => new
{
p.Id,
p.FullName,
p.Catalog.CatalogName,
p.Catalog.Photo.FileName
})
.Where(a => a.Id > 1)
.OrderBy(a => a.FileName)
.ToArray();
// This is OK to filter and order
Expression<Func<Player, bool>> filter = p => p.Id > 1; // will be converted to {p => (p.Id > 1)}
var order1 = "FullName".ToMemberOf<Player>(); // will be converted to {e => Convert(e.FullName, Object)}
var result2 = queryable
.Select<Player>(new[]
{
"Id",
"FullName",
"Catalog.CatalogName",
"Catalog.Photo.FileName"
})
.Where(filter)
.OrderBy(order1)
.ToArray();
// HOW TO ENHANCE ToMemberOf to GET SUCH MEMBER
// This line of code will fail with System.ArgumentException : 'Catalog.Photo.FileName' is not a member of type 'Player'
// var order2 = "Catalog.Photo.FileName".ToMemberOf<Player>();
// I could do as..
// But I could not use it in on the OrderBy clause below
var order2 = "Catalog.Photo.FileName".ToExtendedMemberOf<Player>(); // will be converted to {e => new Player() {Catalog = new Catalog() {Photo = new Photo() {FileName = e.Catalog.Photo.FileName}}}}
var result3 = queryable
.Select<Player>(new[]
{
"Id",
"FullName",
"Catalog.CatalogName",
"Catalog.Photo.FileName"
})
.Where(filter)
//.OrderBy(order2)
.ToArray();
// I could do as..
// But this line of code will with InMemory provider fails with System.InvalidOperationException : Failed to compare two elements in the array. System.ArgumentException : At least one object must implement IComparable.
// But this line of code will with SqlServer provider fails with System.InvalidOperationException : Error generated for warning 'Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning: The LINQ expression 'orderby new Player() {Catalog = new Catalog() {Photo = new Photo() {FileName = e.Catalog.Photo.FileName}}} asc' could not be translated and will be evaluated locally.'.
var result4 = queryable
.Select<Player>(new[]
{
"Id",
"FullName",
"Catalog.CatalogName",
"Catalog.Photo.FileName"
})
.Where(filter)
.OrderBy<Player>(new[]
{
"Catalog.Photo.FileName"
})
.ToArray();
}
}
}
}
除此之外。.我打算使用投影
我将一些具有差异的图像粘贴到返回的对象上,只是为了更好地理解我想要实现的目标。
即result1是应该包含的匿名对象的集合 和result2,result3和result3是Player对象的集合,其中并非所有的属性都已由EF填充,就像我要求使用投影
在下面查看其他相关链接:
Dynamically build select list from linq to entities query
答案 0 :(得分:3)
关于用点分隔的字符串名称(也称为成员路径)为所谓的“内部图”成员(我称为嵌套成员)构建成员选择器表达式。
您的方法MemberOf
:
public static Expression<Func<T, object>> ToMemberOf<T>(this string name) where T : class
{
var parameter = Expression.Parameter(typeof(T), "e");
var propertyOrField = Expression.PropertyOrField(parameter, name);
var unaryExpression = Expression.MakeUnary(ExpressionType.Convert, propertyOrField, typeof(object));
return Expression.Lambda<Func<T, object>>(unaryExpression, parameter);
}
可以轻松调整以处理直接成员和嵌套成员。您所需要做的就是更改一行代码:
var propertyOrField = Expression.PropertyOrField(parameter, name);
到
var propertyOrField = name.Split('.')
.Aggregate((Expression)parameter, Expression.PropertyOrField);
如此
var order2 = "Catalog.Photo.FileName".ToMemberOf<Player>();
将转换为
{e => Convert(e.Catalog.Photo.FileName)}