我可以在LINQ查询中使用扩展方法吗?

11

我有以下代码:

    public QuestionDetail GetQuestionDetail(int questionId)
    {
        Question question = _questionsRepository.GetById(questionId);
        QuestionDetail questionDetail = new QuestionDetail()
        {
            QuestionId = questionId,
            Text = question.Text.FormatCode()
        };
        return questionDetail;
    }

我用这个替换了它:

    public QuestionDetail GetQuestionDetail(int questionId)
    {
        var questions = _questionsRepository
            .GetAll()
            .Include(q => q.Answers)
            .Select(m => new QuestionDetail
            {
                QuestionId = m.QuestionId,
                Text = m.Text.FormatCode()
            })
            .FirstOrDefault();

        return questions;
    }

现在我收到以下错误消息:

LINQ to Entities does not recognize the method 'System.String FormatCode(System.String)' 
method, and this method cannot be translated into a store expression.

这是我的FormatCode()函数。

public static class CodeDisplay {

    public static string FormatCode(this string content)
    {
        var data1 = content
            .Split(new[] { "<pre>", "</pre>" }, StringSplitOptions.None);
        var data2 = data1
            .Select((s, index) =>
            {
                string s1 = index % 2 == 1 ? string.Format("{0}{2}{1}",
                    "<table class='code'>", "</table>", SplitJoin(s)) : s;
                return s1;
            });
        var data3 = data2.Where(s => !string.IsNullOrEmpty(s));
        var data4 = string.Join("\n", data3);
        return data4;
    }

    private static string SplitJoin(string content)
    {
        IEnumerable<String> code =
            content.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
                .Select((line, index) =>
                    string.Format("<tr><td>{0}</td><td><pre><code>{1}</code></pre></td></tr>\n",
                    (index + 1).ToString("D2"), HttpUtility.HtmlEncode(line)));
        return string.Join("", code) + "\n";
    }


}

1
可能是在Linq查询中使用自定义扩展方法的重复问题。 - Rotem
2
可能是重复问题,但我觉得那个问题的答案不是很好。 - driis
3
@Rotem 这个问题更具体,可以得到更具体的回答。 - Danny Varod
3个回答

17

简短回答:是的,您可以在LINQ查询中使用自定义扩展方法,但是您不能使用底层数据提供程序不知道如何执行的扩展方法。

LINQ代表语言集成查询,是C#中的一种语言特性。您可以在LINQ查询中使用任何.NET方法。LINQ本身不知道底层数据存储的细节,这些细节通过IEnumerable<T>IQueryable<T>接口向LINQ公开。当您对实现IQueryable<T>的对象进行查询时,例如Entity Framework表格,该接口公开了底层LINQ提供程序,该提供程序知道如何在Entity Framework / SQL中进行查询。

在查询中使用任何方法时,.NET方法必须具有转换为底层数据提供程序的功能,才能使其正常工作。对于LINQ-to-Objects(不涉及数据库的情况),此转换是微不足道的(即不需要转换),因此您可以使用任何扩展方法。对于LINQ-to-SQL或LINQ-to-Entities(您正在使用的方式),底层数据提供程序必须知道如何将CLR方法转换为底层存储中的表示形式,例如SQL。这是LINQ提供程序的工作。

因此,在您的LINQ-to-Entities查询中,您无法使用自定义扩展方法。为了使其工作,LINQ提供程序需要知道如何在SQL中表示您的方法,但它不知道那个。
一种常见的方法是通过调用其中一个急切方法(例如ToArray()ToList())在底层数据提供程序中执行查询,然后在此之后使用自定义方法进一步细化查询。由于结果是简单的CLR对象,因此使用LINQ-to-Objects,并且您可以使用自定义CLR方法。只需注意,这可能会从数据库中获取许多结果。对于您的示例,您仅获取一个结果,因此这并不重要。
将上述模式应用于您的代码如下:
public QuestionDetail GetQuestionDetail(int questionId)
{
    var questions = _questionsRepository
        .GetAll()
        .Include(q => q.Answers)
        .Take(1)   // Constrain to one result fetched from DB
        .ToArray() // Invoke query in DB
        .Select(m => new QuestionDetail
        {
            QuestionId = m.QuestionId,
            Text = m.Text.FormatCode()
        })
        .FirstOrDefault();

    return questions;
}

1
如果问题有许多字段,这个查询将会导致从数据库中加载冗余数据。在调用 .ToList() 或 .First() 等方法之前,.Select() 应该过滤掉冗余字段。 - Danny Varod
在Select()调用之前应用AsEnumerable()应该在这种情况下就足够了。 - Anders

2

不要试图在LINQ to Entities中运行FormatCode()方法,因为ADO.NET提供程序不知道如何将其翻译成SQL而导致失败。相反,您可以像这样运行尽可能多的查询作为LINQ to Entities:

var questionTmp = _questionsRepository
        .GetAll()
        //.Include(q => q.Answers) // commented out since you aren't selecting this
        .Select(m => new // Anonymous type
        {
            QuestionId = m.QuestionId,
            Text = m.Text, // raw data to be used as input for in-memory processing
        })
        .FirstOrDefault(); // or use .ToList(); if you want multiple results

然后在结果中运行该方法,如下:

// For one
var question = new QuestionDetail
{
    QuestionId = questionTmp.QuestionId,
    Text = questionTmp.Text.FormatCode(),
};

// Or for many
var questions = questionsTmp.Select(q =>
    new QuestionDetail
    {
        QuestionId = q.QuestionId,
        Text = q.Text.FormatCode(),
    });

return question; // or questions;

0
你可以试一下。
public QuestionDetail GetQuestionDetail(int questionId)
{
    Question question = _questionsRepository.GetById(questionId).ToList();
    QuestionDetail questionDetail = new QuestionDetail()
    {
        QuestionId = questionId,
        Text = question.Text.FormatCode()
    };
    return questionDetail;
}

这将把question实例化到内存中,以便您可以应用所需的格式。

@DannyVarod 但它是在函数的参数中提供的。 - Giannis Paraskevopoulos
真的,然而奇怪的是,在原始查询中它没有被使用 :-) - Danny Varod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接