【问题标题】:Linq to entities does not recognize the method system.string get_ItemLinq to entity 无法识别方法 system.string get_Item
【发布时间】:2017-12-06 09:12:30
【问题描述】:

我正在尝试将 orderByExpression 传递给在实体框架中的 dbContext 对象上使用它的函数

> Dictionary<int, string> dict = new Dictionary<int, string>();
> Expression<Func<DbQuestion, string>> orderByExpression = r => dict[r.Id];

这会引发以下错误。

Exception=Exceptions.DBConnectionException: LINQ 到实体不识别方法'System.String get_Item(Int32)' 方法,并且该方法不能翻译成 存储表达式。

我了解我无法访问 Linq to Sql 查询中的 [ ]。您能否建议一个替代方案或为我指明正确的方向?

编辑:

关于我要解决的问题的更多细节:

字典有 15-20 个项目,键保持不变,只有值动态变化

我正在尝试根据 r.Id 的范围来排序问题,即如果 floor(r.Id)==14 则我返回“a”,如果它介于 floor(r.Id)==15 之间,则返回“b”,如果它在 floor(r.id)==13 之间,我返回“c”(由 dict 中的值决定)这有助于对行进行排序

这是实际的表达方式:

List<int> cqh;
List<int> iqh;
Expression<Func<DbQuestion, string>> orderByExpression = r => cqh.Contains(r.QuestionID)? dict[(int)Math.Floor(r.SearchKey1)] +"2"+Guid.NewGuid() :  
                    iqh.Contains(r.QuestionID)? dict[(int)Math.Floor(r.SearchKey1)] + "1"+Guid.NewGuid() :  
                    dict[(int)Math.Floor(r.SearchKey1)] + "0"+Guid.NewGuid();

谢谢,

【问题讨论】:

  • 字典中有多少项?可能很多还是只有几个?
  • 通常15-20件
  • 这些是动态变化的?
  • 哇 .. 这是一个很大的表达。我会更新我的答案,在这种情况下手动构建它不是一个好的选择,我会尝试使用访客来完成。
  • 不,你不能在SQL中调用C#函数,至少不能这样,EF转换查询时会失败

标签: c# sql entity-framework linq


【解决方案1】:

我看到两个选项,与 LINQ to Entities 保持一致

  1. 将字典中的数据放在单独的表中,保存,然后加入表。即使您需要每个查询数据,也可以应用此解决方案。

例子:

public class SortKey
{
    [Key]
    public int SortKeyId { get; set; }
    public long SearchId { get; set; }
    public int EntityId { get; set; }
    public string SortId { get; set; }
}

using (var db = new Db())
{
    Dictionary<int, string> dict = new Dictionary<int, string>();
    long searchId = DateTime.Now.Ticks; // Simplfied, either use a guid or a FK to another table
    db.Keys.AddRange(dict.Select(kv => new SortKey { SearchId = searchId, EntityId = kv.Key, SortId = kv.Value }));
    db.SaveChanges();

    var query = from e in db.Entity
                join k in db.Keys.Where(k => k.SearchId == searchId) on (int)e.Id equals k.EntityId
                orderby k.SortId
                select e;
}

// Cleanup the sort key table 
  1. 动态构建条件。这可以使用表达式操作来实现

例子:

Expression exp = Expression.Constant(""); //Default order key
var p = Expression.Parameter(typeof(Entity));
foreach (var kv in dict)
{
    exp = Expression.Condition(
        Expression.Equal(
            Expression.Convert(
                Expression.MakeMemberAccess(p, p.Type.GetProperty("Id")), typeof(int)
            ),
            Expression.Constant(kv.Key)
        ),
        Expression.Constant(kv.Value),
        exp
    );
}

var orderByExp = Expression.Lambda<Func<Entity, string>>(exp, p);

var query = db.Entity.OrderBy(orderByExp);

您使用哪个选项取决于字典中的数据量。为OrderBy 构建的条件对于大量数据可能会变得非常低效

编辑

根据更改的问题,您可以使用表达式访问者将 dic[...] 调用替换为字典中每个值的条件测试。这种方法的优点是你可以很容易地改变表达式,替换也会以同样的方式工作

班级:

class DictionaryReplaceVisitor : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if(node.Object != null && node.Object.Type == typeof(Dictionary<int, string>) && node.Method.Name == "get_Item")
        {
            Expression exp = Expression.Constant(""); //Default order key
            // Compile the tahrget of the index and execute it to get the value
            // If you know there is a single dictionary you could replace this with a class property intead and set it from the Visit call site, 
            // but this is the more general appraoch
            var dict = Expression.Lambda<Func<Dictionary<int, string>>>(node.Object).Compile()();
            foreach (var kv in dict)
            {
                exp = Expression.Condition(
                    Expression.Equal(
                        node.Arguments.Single(),
                        Expression.Constant(kv.Key)
                    ),
                    Expression.Constant(kv.Value),
                    exp
                );
            }

            return exp;
        }
        return base.VisitMethodCall(node);
    }
}

用法:

Expression<Func<Entity, string>> orderByExpression = r => cqh.Contains(r.QuestionID) ? dict[(int)Math.Floor(r.SearchKey1)] + "2" + Guid.NewGuid() :
        iqh.Contains(r.QuestionID) ? dict[(int)Math.Floor(r.SearchKey1)] + "1" + Guid.NewGuid() :
        dict[(int)Math.Floor(r.SearchKey1)] + "0" + Guid.NewGuid();
var replace = (Expression<Func<Entity, string>>)new DictionaryReplaceVisitor().Visit(orderByExpression);

var query = db.Entity.OrderBy(replace).ToString();

生成的 SQL 不会很漂亮,但它应该可以工作。

解决方案 3:

如果数据量不是很大,可以在查询上做ToListAsEnumerable,在内存中进行排序(在上述方法之一之后调用OrderBy)。在这种情况下它实际上可能表现更好

【讨论】:

  • 可以稍微改进一下,因为 OP 需要根据 id 范围而不是单个 id 进行排序。但这当然回答了实际提出的问题。
  • @Evk 根据 cmets,您是对的。但是我读了没有提到范围的问题,只有字典并开始编写代码。
  • @TitianCernicova-Dragomir 我真的很抱歉浪费了同行开发人员的时间。让我在问题中添加更多细节
  • @router 我从 cmets 了解到,最终排序键将更多地基于范围而不是字典。这两种解决方案原则上仍然适用,您需要在表中具有第一个的范围,而对于第二个您需要建立范围条件。如果您添加详细信息,我可以更改代码
  • @router 我没有看到。让这个问题再开放一段时间,也许其他人会想出一些东西。
猜你喜欢
  • 1970-01-01
  • 2013-04-07
  • 1970-01-01
  • 2017-01-02
  • 2019-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多