2016-12-11 3 views
2

Ich habe einen WebAPI-Dienst mit einem SQL Server-Backend, der mit Entity Framework (Code-first) verwaltet wird. Ich habe Linq-Abfragen verwendet, um Daten aus der Datenbank abzurufen. Da einige meiner API-Endpunkte mehrere Joins durchführen, um Daten zu erhalten, habe ich bei der Bereitstellung in der Produktion Verzögerungen beobachtet. Ich lese mit ORM-Mapping vs direkte SQL-Abfragen (gespeicherte Prozeduren) und kam zu der Schlussfolgerung, dass für komplexe Abfragen optimal ist, gespeicherte Prozeduren zu verwenden.Endergebnis von gespeicherter Prozedur im Entity Framework zurückgeben

stieß ich auf einigen Artikel erklären, wie aus gespeicherten Prozedur zurückzuWertCode wie Verwendung:

context.Database.SqlQuery<T>(...) 

jedoch alle Beispiele befassen dich mit einfacher SELECT Abfrage und ja, ich mehr SELECT Abfrage-Ergebnisse auch kam über Rückkehr auch. Aber in diesem Fall ist es 2 SELECT Abfragen.

Im realen Szenario kann es mehr als 2 Select-Abfragen sein, da es sich um Joins und andere Codes handelt. Und am Ende der Abfrage werden wir nur die letzte SELECT Abfrage ableiten, die die erwarteten Daten zurückgibt. Im Folgenden finden Sie eine Beispielcode Mock-up das Szenario zu erklären:

// Inside my stored procedure 
Declare @XId uniqueidentifier 
Declare @YId uniqueidentifier 

// Some mockup queries. This can have JOINS and other complex codes in real code. 
// Below code is made as simple as possible to explain the scenario 
Select @XId=Id From Table1 Where Name = 'Name1' 
Select @YId=Id From Table2 Where Code = 'Code1' 

// This is the data, I am interested 
Select * From Table3 Where [email protected] And [email protected] 

Wie kann ich Zeilen aus dem letzten SELECT Abfrage dieses Ziel zu erreichen?

Ich habe einige Nachforschungen angestellt und gehe davon aus, dass es mit EF nicht möglich ist, da die Unterstützung für gespeicherte Prozeduren minimal ist. Ist dies innerhalb von EF-Limits möglich oder muss ich dafür auf ADO.NET zurückgreifen?

Jede Hilfe wird geschätzt.

Danke.

+0

@GertArnold - Ich habe Beispielcode-Schnipsel Erläuterung des Szenario hinzugefügt. – abhilashca

+0

Ich denke, du solltest 'Select *' durch 'Select [deine tatsächliche Spaltenliste]' ersetzen, damit du tatsächlich weißt, mit was du arbeitest. Erstellen Sie dann einen Typ 'MyResultType', dessen Eigenschaften mit den ausgewählten Spalten übereinstimmen, und rufen Sie' context.Database.SqlQuery (...) 'auf. – grek40

+0

Ja, ich habe das schon ausprobiert, aber es wird nach 'Table1DTO' und nicht nach' Table3DTO' geworfen, was natürlich zu einem Casting-Fehler führt. – abhilashca

Antwort

1

I hat es irgendwie geschafft, nach dem Lesen des MSDN-Artikels eine Lösung abzuleiten - https://msdn.microsoft.com/en-us/data/jj691402.aspx. Hier ist was ich getan habe.

Ich führte gespeicherte Prozedur mit DbCommand.ExecuteReader(), die DbDataReader zurückgeben. Da ist mein Point-of-Interest die letzte SELECT Aussage. Ich schaffte es, zum letzten Resultset von DbCommandReader zu springen und das zu meinem DTO unter Verwendung ObjectContext.Translate<T>() zu werfen.

Nicht sicher, ob es irgendwelche besseren optional verfügbaren gibt. Vorläufig funktioniert es.

Update: Ich wechselte schließlich zu Dapper, die ein Mikro ORM von Stack ist. Im Vergleich zu EF gibt es eine erhebliche Leistungssteigerung. Der API-Aufruf, der 3 Sekunden mit EF dauerte, gibt jetzt Daten in weniger als 500 ms zurück.

Danke.

+0

Ich habe mir auch diesen Artikel angesehen, als ich meine Antwort geschrieben habe. Die Verwendung des Objektkontexts hat mir definitiv beim Debuggen geholfen, da seine Methoden einige hilfreichere Ausnahmen enthalten. Aber sobald die Probleme aussortiert sind, denke ich, dass 'SqlQuery' die sauberere Lösung ist, verglichen mit der Rückkehr zum Objektkontext. – grek40

+0

Haben Sie einen Vorschlag, welche Art von Leistung Sie implementieren sollen? Da ich mich mit ExecuteReader() befasse, könnte ich genau die SELECT-Anweisung wählen, die ich für die Eingabe von T schreiben muss. Wenn ich "SqlQuery" wähle, dann muss das System den Rückgabetyp "T" mit jeder "Select" -Anweisung vergleichen, um zu sehen, welche die vor dem Casting ist. Hat das irgendwelche Leistungsprobleme? Irgendwelche Gedanken? – abhilashca

+0

Da Sie nur an der letzten Abfrage interessiert sind, möchten Sie möglicherweise die Abfrage als einzige neu entwerfen, die Ergebnisse zurückgibt. Vielleicht sehen Sie sich die Klausel ['Mit - Als - Auswählen'] (https://msdn.microsoft.com/de-de/library/ms175972.aspx) an, um die internen Vorauswahlen Ihrer Prozedur zu ersetzen. Bearbeite ein SP-Beispiel in meine Frage. – grek40

0

Da Sie nicht viel von Ihrem Code offengelegt haben, kann ich Ihnen nur ein funktionierendes Beispiel zur Verfügung stellen.

Angenommen, Sie das folgende CodeFirst Schema in der Datenbank

class DbC : DbContext 
{ 
    public DbC() 
    { 
    } 
    public DbSet<Blog> Blogs { get; set; } 
} 

class Blog 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public List<Post> Posts { get; set; } 
} 

class Post 
{ 
    public int Id { get; set; } 
    public string Text { get; set; } 
} 

Ein paar Daten, die ich davon ausgehen, wohnen haben:

var tmp = new List<Blog>() 
{ 
    new Blog 
    { 
     Name = "Blog1", 
     Posts = new List<Post>() 
     { 
      new Post { Text = "Post1" }, 
      new Post { Text = "Post2" }, 
      new Post { Text = "Post3" }, 
     } 
    }, 
    new Blog 
    { 
     Name = "Blog2", 
     Posts = new List<Post>() 
     { 
      new Post { Text = "Post4" }, 
      new Post { Text = "Post5" }, 
      new Post { Text = "Post6" }, 
     } 
    }, 
}; 

Und eine gespeicherte Prozedur zum Abrufen Posts von Blog.Name

CREATE PROCEDURE [dbo].[PostsRetrieverProc] 
    @BlogName NVARCHAR (MAX) 
AS 
    Declare @BlogId int = 0 

    Select @BlogId=Id From [Blogs] Where Name = @BlogName 

    Select 
     b.Id [BlogId], 
     p.Id [PostId], 
     p.[Text] [PostText] 
    From [Blogs] b join [Posts] p On b.Id = p.Blog_Id 
    Where p.Blog_Id = @BlogId 
GO 

Dann können Sie ein Arb erstellen itrary Klasse mit Eigenschaften, deren Namen und Typen Prozedur Ergebnis

public class MyCustomReturnType 
{ 
    public int BlogId { get; set; } 
    public int PostId { get; set; } 
    public string PostText { get; set; } 
} 

übereinstimmen und die gespeicherte Prozedur aufrufen einige Werte zurückzukehren

using (var db = new DbC()) 
{ 
    var result = db.Database.SqlQuery<MyCustomReturnType>("dbo.PostsRetrieverProc @param1", new SqlParameter("param1", "Blog2")); 
    foreach (var item in result) 
    { 
     Console.WriteLine(item.PostText); 
    } 
} 

Mein Ergebnis Ausgabe wie erwartet:

Post4 
Post5 
Post6 

Irgendwelche Probleme mit dem Ansatz?

Edit:

Da Sie nur das letzte Ergebnis aus dem gespeicherten Prozedur wollen, könnte es besser sein, die SP neu zu gestalten, anstatt die Zwischenergebnisse des Überspringens. Dies kann mit dem With clause erfolgen. Beachten Sie, dass dieses Beispiel Code ein bisschen aufgebläht ist, da ich nur den Blog-ID verwenden und kommen immer noch mit [Blogs] nur um zu zeigen, dass seine mögliche

CREATE PROCEDURE [dbo].[PostsRetrieverProc] 
    @BlogName NVARCHAR (MAX) 
AS 
    With 
     BlogEntries (BlogId) 
    As 
     (Select Id BlogId From [Blogs] Where Name = @BlogName) 
    Select 
     b.Id [BlogId], 
     p.Id [PostId], 
     p.[Text] [PostText] 
    From BlogEntries e 
    join [Blogs] b On e.BlogId = b.Id 
    join [Posts] p On b.Id = p.Blog_Id 

Auch interessant für mehrere Vorauswahl zu ermöglichen: Can I use multiple "with"?

+0

Funktioniert dieser Code? Ich habe letztens dasselbe versucht, aber es hat nicht funktioniert. Lass mich das gleiche mit deiner Probe versuchen. Vielen Dank. – abhilashca

+0

Ich musste besonders vorsichtig sein, um 'BlogId' im SP den Standardwert zuzuweisen, um die SP-Parameter in die Befehlszeichenfolge aufzunehmen (verlasse dich nicht auf' SqlParameter' mit dem gleichen Namen), aber ja, ich habe das wirklich ausgeführt und es hat für mich funktioniert, kein hypothetisches Beispiel. – grek40

+0

Ich habe die Probe versucht. Hier ist, was ich habe. 'ErrorMessage = Ein Fehler ist aufgetreten., Der Datenleser ist nicht kompatibel mit dem angegebenen 'MY_TYPE_NAME_HERE'. Ein Member vom Typ 'PROPERTY_NAME_HERE' hat keine entsprechende Spalte im Datenreader mit demselben Namen., Unter System.Data.Entity.Core.Query.InternalTrees.ColumnMapFactory.GetMemberOrdinalFromReader (DbDataReader storeDataReader, EdmMember-Member, EdmType currentType, Dictionary '2 renameList)'. – abhilashca

0

Verwenden Sie nicht nur ausführen.

Verwendung dieses sql:

Insert into #tempTable 
execute sp_executesql @SELECT 
Verwandte Themen