LINQ to Entities, 'System.String ToString ()' yöntemini tanımıyor ve bu yöntem bir mağaza ifadesine çevrilemez


126

Bazı şeyleri bir mysql sunucusundan bir sql sunucusuna taşıyorum ancak bu kodun nasıl çalışmasını sağlayacağımı bulamıyorum:

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();
}

İkinciye girdiğinde foreach (var page in pages)şöyle bir istisna atar:

LINQ to Entities, 'System.String ToString ()' yöntemini tanımıyor ve bu yöntem bir mağaza ifadesine çevrilemez.

Bunun neden olduğunu bilen var mı?


Yanıtlar:


134

Dizeyi geçici bir değişkene kaydedin ve ardından bunu ifadenizde kullanın:

var strItem = item.Key.ToString();

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

Sorun ToString(), gerçekten çalıştırılmadığı için ortaya çıkar , bir MethodGroup'a dönüştürülür ve daha sonra ayrıştırılır ve SQL'e çevrilir. ToString()Eşdeğeri olmadığı için ifade başarısız olur.

Not:

Daha sonra eklenen yardımcı sınıfla ilgili olarak Alex'in cevabını da kontrol ettiğinizden emin olun SqlFunctions. Çoğu durumda geçici değişken ihtiyacını ortadan kaldırabilir.


14
Ya ToString (), eşitliğin sol tarafına uygulanıyorsa? egpSerial.ToString () = öğe.
dotNET

3
@dotNet Bu, Entity Framework'ün geçerli SQL'e dönüştürmeye çalıştığı bir İfadeye dönüştüğü için yine de başarısız olacaktır. Nasıl başa çıkılacağını bildiği bazı yöntemler vardır, ancak ToString()onlardan biri değildir.
Josh

7
@ Josh: Başarısız olacağını anlıyorum. İstediğim şey bu senaryonun bir çözümü, çünkü yukarıdaki çözüm açıkçası orada uygulanamaz.
dotNET

3
@Josh: Böyle bir senaryoyla mücadele ediyorum. SiparişNumarası sütunumun int olduğunu, ancak kullanıcım yazarken Sipariş Numaraları listesini filtreleyebilmek istiyor. Arama kutusuna 143 yazdıysa, yalnızca Sipariş Numarası '% 143%' GİBİ olan kayıtları istiyor . Bunu başarmak için OrderNumber sütununda ToString () yapmam gerekmiyor mu?
dotNET

5
@dotNET bu, bir ORM'nin yüzüne düştüğü senaryolardan biridir. Bence bu durumlarda ya düz SQL'e ExecuteQueryya da Entity SQL kullanarakObjectQuery<T>
Josh

69

Diğerlerinin de yanıtladığı gibi, bu durum bozulur çünkü .ToString, veritabanına giderken ilgili SQL'e çeviremez.

Ancak Microsoft, bunun gibi durumlarda kullanılabilecek yöntemler koleksiyonu olan SqlFunctions sınıfını sağlar .

Bu durum için, burada aradığınız şey SqlFunctions.StringConvert :

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

Her ne sebeple olursa olsun geçici değişkenlerle çözüm istenmediğinde iyidir.

SqlFunctions'a benzer şekilde , aynı zamanda veri kaynağı agnostik olan (örneğin SQL ile sınırlı olmayan) farklı bir işlev kümesi sağlayan EntityFunctions'a da (DbFunctions tarafından geçersiz kılınan EF6 ile ) sahip olursunuz .


4
SqlFunctions sınıfını .NET 4'e geri eklediler ve bunu yeni mi öğreniyorum? Mükemmel bul.
James Skemp

24

Sorun, bir LINQ to Entities sorgusunda ToString'i çağırmanızdır. Bu, ayrıştırıcının ToString çağrısını eşdeğer SQL'e dönüştürmeye çalıştığı anlamına gelir (bu mümkün değildir ... dolayısıyla istisna).

Tek yapmanız gereken ToString çağrısını ayrı bir hatta taşımak:

var keyString = item.Key.ToString();

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

9

Benzer bir sorun vardı. Varlık koleksiyonunda ToList () çağırarak ve listeyi sorgulayarak çözdü. Koleksiyon küçükse bu bir seçenektir.

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

Bu yardımcı olur umarım.


42
Lütfen bunun tüm Sayfa varlıklarını veritabanından alacağını ve filtrelemeyi db yerine istemci tarafında yapacağını unutmayın .. genellikle iyi bir şey değildir.
lambinator

3
Bu yöntemin birden fazla kayıt içeren herhangi bir tablo için etkisiz olacağı doğrudur, yani mevcut tüm tablolar :-). Ancak, bu cevap bugün bana yardımcı oldu çünkü toString () 'i içeren bir .Select projeksiyonu yapıyordum, bu yüzden elden önce .ToList ()' i çağırmak benim için hiçbir performans cezasına sahip değildi ve .ToList () 'i çağırmak .ToString ()' i kullanmama izin verdi biçimlendirme ve benim .Select deyimi ...
Nathan Prather

6

Bunu böyle değiştirin ve işe yaramalı:

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

İstisnanın LINQ sorgusunun bildirildiği satırda atılmamasının nedeni, ancak satırında foreachertelenmiş yürütme özelliğidir, yani, LINQ sorgusu siz sonuca erişmeye çalışana kadar yürütülmez. Ve bu foreachdaha önce değil de olur .


6

Tabloyu Enumerableşuraya çevirin, ardından ToString()içindeki yöntemi kullanarak LINQ yöntemlerini çağırın :

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

Ancak, yöntem çağırırken AsEnumerableveya ToListyöntem kullanırken dikkatli olun çünkü bu yöntemden önce tüm varlıklardan tüm verileri isteyeceksiniz. Yukarıdaki durumumda tüm table_namesatırları tek bir istekle okudum .


5
normalde iyi bir seçim değildir, .AsEnumerable () burada bu konuda daha fazla görebilirsiniz bellekteki tüm verileri koyun: stackoverflow.com/questions/3311244/...
kavain

5

Entity Framework Sürüm 6.2.0'a yükseltme benim için çalıştı.

Daha önce 6.0.0 sürümündeydim.

Bu yardımcı olur umarım,


1

MVC'de, gereksinimlerinize veya bilgilerinize göre kayıtları aradığınızı varsayın. Düzgün çalışıyor.

[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
Daha iyi bir uygulama için veya üretim kod türlerinde, veritabanı olaylarını her zaman doğrudan eylemde değil, bir hizmet katmanında veya veri katmanında bulundurmalısınız.
TGarrett

0

ToStringSorgunuzun içine gerçekten yazmak istiyorsanız, çağrıyı uygun işleveToString bir çağrıStringConvert ile yeniden yazan bir ifade ağacı ziyaretçisi yazabilirsiniz :

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);

        }
    }
}

FirstOrDefault'u kullanıyor olmalı, sadece First değil ... Eğer birincil anahtar ise, daha iyi performans gösterdiği için Find'ı kullanın.
TGarrett

@TGarrett Buradaki tek kullanım Firstsonuçları GetMethods()dönen sonuçlar üzerinedir MethodInfo[]. AFAIK'in MethodInfo[]bir Findyöntemi yoktur, böyle bir genişletme yöntemi de yoktur. Ama gerçekten kullanmalıyım Singleçünkü bu yöntem yansıtma yoluyla bulunur ve uygun yöntem çözülemezse derleme zamanı hatası olmaz.
Zev Spitz

0

Bu durumda aynı hatayı aldım:

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);

Hata ayıklamak için çok fazla zaman harcadıktan sonra, hatanın mantık ifadesinde göründüğünü anladım.

İlk satır search.Contains(log.Id.ToString())iyi çalışıyor, ancak bir DateTime nesnesiyle ilgilenen son satır, sefil bir şekilde başarısız olmasına neden oldu:

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

Sorunlu çizgiyi kaldırın ve sorun çözüldü.

Nedenini tam olarak anlamıyorum, ancak ToString () dizeler için bir LINQ ifadesi gibi görünüyor, ancak Varlıklar için değil. LINQ for Entities, SQL gibi veritabanı sorgularıyla ilgilenir ve SQL, ToString () kavramına sahip değildir. Bu nedenle, ToString () öğesini bir .Where () yan tümcesine atamayız.

Peki ilk satır nasıl işliyor? ToString () yerine SQL'de CASTve var CONVERT, bu yüzden şimdiye kadarki en iyi tahminim, varlıklar için linq'in bazı basit durumlarda bunu kullanmasıdır. DateTime nesneleri her zaman bu kadar basit bulunmaz ...


-8

LINQ sorgunuzda bir yöntem çağrısı kullanmanız gerektiğinde, LINQ to Entity sorgusunu LINQ to Objects sorgusuna dönüştürün (örneğin ToArray çağırın).


3
"Ne zaman bir yöntem çağrısı kullanmanız gerekirse", kötü bir tavsiye - birçok kayıtla bu büyük bir sorun olabilir. Kabul edilen cevap, bu senaryo için çok daha iyidir.
PeteGO
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.