LINQ to Entities không nhận ra phương thức 'System.String ToString ()' và phương thức này không thể được dịch thành biểu thức lưu trữ


126

Tôi đang di chuyển một số thứ từ một máy chủ mysql sang máy chủ sql nhưng tôi không thể tìm ra cách làm cho mã này hoạt động:

using (var context = new Context())
{
    ...

    foreach (var item in collection)
    {
        IQueryable<entity> pages = from p in context.pages
                                   where  p.Serial == item.Key.ToString()
                                   select p;
        foreach (var page in pages)
        {
            DataManager.AddPageToDocument(page, item.Value);
        }
    }

    Console.WriteLine("Done!");
    Console.Read();
}

Khi nó bước vào giây thứ hai, foreach (var page in pages)nó ném ra một ngoại lệ:

LINQ to Entities không nhận ra phương thức 'System.String ToString ()' và phương thức này không thể được dịch thành biểu thức lưu trữ.

Co ai biêt tại sao điêu nay xảy ra?


Bài liên quan - LINQ to Entity
RBT

Câu trả lời:


134

Chỉ cần lưu chuỗi vào một biến tạm thời và sau đó sử dụng chuỗi đó trong biểu thức của bạn:

var strItem = item.Key.ToString();

IQueryable<entity> pages = from p in context.pages
                           where  p.Serial == strItem
                           select p;

Vấn đề phát sinh do ToString()không thực sự được thực thi, nó được chuyển thành Phương thức nhóm và sau đó phân tích cú pháp và dịch sang SQL. Vì không có ToString()tương đương, biểu thức thất bại.

Ghi chú:

Hãy chắc chắn rằng bạn cũng kiểm tra câu trả lời của Alex về SqlFunctionslớp người trợ giúp đã được thêm vào sau đó. Trong nhiều trường hợp, nó có thể loại bỏ sự cần thiết của biến tạm thời.


14
Điều gì xảy ra nếu ToString () của tôi đang được áp dụng ở phía bên trái của đẳng thức? egpSerial.ToString () = item.
dotNET

3
@dotNet Điều đó vẫn sẽ thất bại vì toàn bộ điều được chuyển thành Biểu thức, mà Entity Framework cố gắng biến thành SQL hợp lệ. Có một số phương pháp nó biết cách xử lý, nhưng ToString()không phải là một trong số đó.
Josh

7
@Josh: Tôi hiểu rằng nó sẽ thất bại. Những gì tôi đã yêu cầu là một giải pháp của kịch bản đó, bởi vì giải pháp trên rõ ràng không thể được áp dụng ở đó.
dotNET

3
@Josh: Tôi đang vật lộn với một phối cảnh như vậy. Giả sử cột OrderNumber của tôi là int, nhưng người dùng của tôi muốn có thể lọc danh sách OrderNumbers khi anh ta nhập. Nếu anh ta đã gõ 143 vào hộp tìm kiếm, anh ta chỉ muốn những bản ghi có OrderNumber THÍCH '% 143%' . Tôi không cần phải làm ToString () trên cột OrderNumber để đạt được nó?
dotNET

5
@dotNET đây là một trong những tình huống mà ORM rơi xuống trên mặt của nó. Tôi nghĩ rằng trong những tình huống đó có thể chuyển sang SQL thẳng thông qua ExecuteQueryhoặc bằng cách sử dụng SQL thực thể vớiObjectQuery<T>
Josh

69

Như những người khác đã trả lời, điều này bị phá vỡ vì .ToString không dịch sang SQL có liên quan trên đường vào cơ sở dữ liệu.

Tuy nhiên, Microsoft cung cấp lớp SqlFifts là tập hợp các phương thức có thể được sử dụng trong các tình huống như thế này.

Trong trường hợp này, thứ bạn đang tìm kiếm ở đây là SqlFifts.StringConvert :

from p in context.pages
where  p.Serial == SqlFunctions.StringConvert((double)item.Key.Id)
select p;

Tốt khi giải pháp với các biến tạm thời là không mong muốn vì bất kỳ lý do gì.

Tương tự như SqlFifts, bạn cũng có EntityFifts (với EF6 bị lỗi bởi DbFifts ) cung cấp một tập hợp các hàm khác nhau cũng là bất khả tri về nguồn dữ liệu (không giới hạn ở ví dụ SQL).


4
Họ đã thêm lớp SqlFifts trở lại trong .NET 4 và tôi chỉ đang tìm hiểu về nó? Tuyệt vời tìm thấy.
James Skemp

24

Vấn đề là bạn đang gọi ToString trong truy vấn LINQ to Entities. Điều đó có nghĩa là trình phân tích cú pháp đang cố gắng chuyển cuộc gọi ToString thành SQL tương đương của nó (điều này là không thể ... do đó là ngoại lệ).

Tất cả bạn phải làm là di chuyển cuộc gọi ToString sang một dòng riêng:

var keyString = item.Key.ToString();

var pages = from p in context.entities
            where p.Serial == keyString
            select p;

9

Có một vấn đề tương tự. Đã giải quyết nó bằng cách gọi ToList () trên bộ sưu tập thực thể và truy vấn danh sách. Nếu bộ sưu tập nhỏ thì đây là một lựa chọn.

IQueryable<entity> pages = context.pages.ToList().Where(p=>p.serial == item.Key.ToString())

Hi vọng điêu nay co ich.


42
Xin lưu ý rằng điều này sẽ truy xuất tất cả các thực thể Trang từ cơ sở dữ liệu và thực hiện lọc ở phía máy khách thay vì db .. thường không phải là một điều tốt.
lambinator

3
Đúng là phương pháp này sẽ không hiệu quả đối với bất kỳ bảng nào chứa nhiều hơn một bản ghi, nghĩa là tất cả các bảng tồn tại :-). Tuy nhiên, câu trả lời này đã giúp tôi hôm nay vì tôi đang thực hiện phép chiếu .Select bao gồm toString () nên việc gọi .ToList () trước đó không có hình phạt hiệu năng nào đối với tôi và gọi .ToList () cho phép tôi sử dụng .ToString () định dạng và tuyên bố .Select của tôi ...
Nathan Prather

6

Thay đổi nó như thế này và nó sẽ hoạt động:

var key = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
                           where  p.Serial == key
                           select p;

Lý do tại sao ngoại lệ không được ném vào dòng truy vấn LINQ được khai báo nhưng trong dòng của foreachtính năng thực thi bị trì hoãn, tức là truy vấn LINQ không được thực thi cho đến khi bạn cố gắng truy cập kết quả. Và điều này xảy ra trong foreachvà không sớm hơn.


6

Truyền bảng tới Enumerable, sau đó bạn gọi các phương thức LINQ bằng cách sử dụng ToString()phương thức bên trong:

    var example = contex.table_name.AsEnumerable()
.Select(x => new {Date = x.date.ToString("M/d/yyyy")...)

Nhưng hãy cẩn thận, khi bạn gọi AsEnumerablehoặc ToListphương thức vì bạn sẽ yêu cầu tất cả dữ liệu từ tất cả các thực thể trước phương thức này. Trong trường hợp của tôi ở trên, tôi đọc tất cả table_namecác hàng theo một yêu cầu.


5
thường không phải là một lựa chọn tốt, .AsEnumerable () đưa toàn bộ dữ liệu trong bộ nhớ, bạn có thể xem thêm về nó ở đây: stackoverflow.com/questions/3311244/...
kavain

5

Nâng cấp lên Entity Framework Phiên bản 6.2.0 làm việc với tôi.

Tôi đã ở phiên bản 6.0.0 trước đây.

Hi vọng điêu nay co ich,


1

Trong MVC, giả sử bạn đang tìm kiếm (các) bản ghi dựa trên yêu cầu hoặc thông tin của bạn. Nó đang hoạt động đúng.

[HttpPost]
[ActionName("Index")]
public ActionResult SearchRecord(FormCollection formcollection)
{       
    EmployeeContext employeeContext = new EmployeeContext();

    string searchby=formcollection["SearchBy"];
    string value=formcollection["Value"];

    if (formcollection["SearchBy"] == "Gender")
    {
        List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Gender == value).ToList();
        return View("Index", emplist);
    }
    else
    {
        List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Name == value).ToList();
        return View("Index", emplist);
    }         
}

2
Để thực hành tốt hơn, hoặc trong các loại mã sản xuất, bạn phải luôn có các sự kiện cơ sở dữ liệu trong lớp dịch vụ hoặc lớp dữ liệu và không trực tiếp trong hành động.
TGarrett

0

Nếu bạn thực sự muốn nhập ToStringvào bên trong truy vấn của mình, bạn có thể viết một khách truy cập cây biểu thức viết lại cuộc gọi đến ToStringvới một cuộc gọi đến StringConverthàm thích hợp :

using System.Linq;
using System.Data.Entity.SqlServer;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System;

namespace ToStringRewriting {
    class ToStringRewriter : ExpressionVisitor {
        static MethodInfo stringConvertMethodInfo = typeof(SqlFunctions).GetMethods()
                 .Single(x => x.Name == "StringConvert" && x.GetParameters()[0].ParameterType == typeof(decimal?));

        protected override Expression VisitMethodCall(MethodCallExpression node) {
            var method = node.Method;
            if (method.Name=="ToString") {
                if (node.Object.GetType() == typeof(string)) { return node.Object; }
                node = Call(stringConvertMethodInfo, Convert(node.Object, typeof(decimal?));
            }
            return base.VisitMethodCall(node);
        }
    }
    class Person {
        string Name { get; set; }
        long SocialSecurityNumber { get; set; }
    }
    class Program {
        void Main() {
            Expression<Func<Person, Boolean>> expr = x => x.ToString().Length > 1;
            var rewriter = new ToStringRewriter();
            var finalExpression = rewriter.Visit(expr);
            var dcx = new MyDataContext();
            var query = dcx.Persons.Where(finalExpression);

        }
    }
}

Nên sử dụng FirstOrDefault và không chỉ Đầu tiên ... Nếu đó là khóa chính, thì hãy sử dụng Tìm, vì nó hoạt động tốt hơn.
TGarrett

@TGarrett Cách sử dụng duy nhất Firstở đây là kết quả GetMethods()trả về MethodInfo[]. AFAIK, MethodInfo[]không có Findphương pháp, cũng không có phương pháp mở rộng như vậy. Nhưng tôi thực sự nên sử dụng Singlebởi vì phương pháp này được tìm thấy thông qua sự phản chiếu và sẽ không có lỗi thời gian biên dịch nếu phương pháp thích hợp không thể được giải quyết.
Zev Spitz

0

Tôi đã gặp lỗi tương tự trong trường hợp này:

var result = Db.SystemLog
.Where(log =>
    eventTypeValues.Contains(log.EventType)
    && (
        search.Contains(log.Id.ToString())
        || log.Message.Contains(search)
        || log.PayLoad.Contains(search)
        || log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
    )
)
.OrderByDescending(log => log.Id)
.Select(r => r);

Sau khi dành quá nhiều thời gian để gỡ lỗi, tôi phát hiện ra rằng lỗi xuất hiện trong biểu thức logic.

Dòng đầu tiên search.Contains(log.Id.ToString())hoạt động tốt, nhưng dòng cuối cùng liên quan đến đối tượng DateTime đã khiến nó thất bại thảm hại:

|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)

Loại bỏ các dòng có vấn đề và giải quyết vấn đề.

Tôi không hoàn toàn hiểu lý do tại sao, nhưng có vẻ như ToString () là biểu thức LINQ cho chuỗi, nhưng không phải cho Thực thể. LINQ cho các thực thể xử lý các truy vấn cơ sở dữ liệu như SQL và SQL không có khái niệm về ToString (). Như vậy, chúng ta không thể ném ToString () vào mệnh đề .Where ().

Nhưng làm thế nào sau đó dòng đầu tiên hoạt động? Thay vì ToString (), SQL có CASTCONVERT, do đó, dự đoán tốt nhất của tôi cho đến nay là linq cho các thực thể sử dụng điều đó trong một số trường hợp đơn giản. Các đối tượng DateTime không phải lúc nào cũng được tìm thấy đơn giản như vậy ...


-8

Chỉ cần biến truy vấn LINQ to Entity thành truy vấn LINQ to Object (ví dụ: gọi ToArray) bất cứ khi nào bạn cần sử dụng một cuộc gọi phương thức trong truy vấn LINQ của mình.


3
"Bất cứ lúc nào bạn cần sử dụng một cuộc gọi phương thức" là lời khuyên tồi - với nhiều hồ sơ, đây có thể là một vấn đề lớn. Câu trả lời được chấp nhận là tốt hơn nhiều cho kịch bản này.
PeteGO
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.