2014-02-13 10 views
13

Ist es möglich, einen TVF in EF6 Code First anzurufen?Unterstützung für Tabellenwertfunktionen im EF6-Code zuerst?

Ich begann ein neues Projekt mit EF6-Datenbank zuerst und EF konnte eine TVF in das Modell importieren und es gut nennen.

Aber das Aktualisieren des Modells wurde sehr zeitaufwendig und problematisch mit der großen schreibgeschützten db ohne RI, mit dem ich festhalte.

Also habe ich zuerst versucht, mit dem Power Tools Reverse Engineering Tool in EF6-Code zu konvertieren, um einen Kontext und Modellklassen zu generieren.

Leider hat das Reverse Engineering Tool die TVFs nicht importiert.

Als nächstes habe ich versucht, die DBFunctions von meinem alten Database First DbContext in den neuen Code First DbContext zu kopieren, aber das gab mir einen Fehler, dass mein TVF: "kann nicht in einen gültigen Typ oder Funktion aufgelöst werden".

Ist es möglich, einen Code zuerst Fluent-Mapping für TVFs zu erstellen?

Wenn nicht, gibt es einen Workaround?

Ich denke, ich könnte SPs anstelle von TVFs verwenden, aber ich hoffte, dass ich hauptsächlich TVFs verwenden könnte, um mit der problematischen DB zu arbeiten, mit der ich feststecke.

Vielen Dank für alle Behelfslösung Ideen

+1

Ich denke nicht, zumindest das ist der Grund, warum ich immer noch Modell zuerst, aber um das Problem mit großen Modellen zu umgehen, importieren Sie nicht alles in ein Modell, sondern kleinere Domain-spezifische Proxies/Clients – peter

+0

Anscheinend können Sie: http://msdn.microsoft.com/en-ca/data/hh859577.aspx – ESG

+0

Danke für die Gedanken zahorak, kann ich zurück zu diesem Ansatz mit DataBase zuerst, wenn ich nicht eine Arbeit finden kann um Code zuerst. Meine Absicht war, die Reverse-Engineering-Tools zu verwenden, um nur die TVFs zu importieren, die ich brauche, und nur diese zu verwenden, um auf die große, böse, hässliche db zuzugreifen, die ich nicht von einem ersten Code-Kontext ändern kann. – Patrick

Antwort

2

ich tatsächlich begann etwas schauen hinein in EF6.1 und haben die Nightly Builds arbeitet. Überprüfen Sie this und this aus.

+0

Ich habe gerade CodeFirstFunctions mit der offiziellen Veröffentlichung von EF 6.1 versucht und ich kann bestätigen, dass es großartig funktioniert. Es scheint ein paar Vorbehalte zu geben, aber ich konnte eine zusammensetzbare Funktion erstellen, die ohne Probleme einen komplexen Typ zurückgibt. Der Ende-zu-Ende-Komponententest, der in dem Projekt bereitgestellt wird, ist ein perfektes Beispiel dafür, wie es zu erledigen ist. – EricTheRed

+0

Danke für die Information - großartig, das zu hören. Ich bereite ein NuGet-Paket vor, damit Sie es "out of the box" verwenden können (d. H. Ohne selbst kompilieren zu müssen) und auch einen Blog-Beitrag, wie Sie es verwenden können. Kannst du mehr über die Vorbehalte wissen? Waren sie Bugs oder mehr über den Mangel an Dokumentation? – Pawel

+0

Das einzige Problem, auf das ich stieß, war, dass mein TVF nullbare Parameter hatte.Das führte zu einem Fehler, an den ich mich nicht erinnern kann, aber es führte mich in den Code, wo ich einige TODOs über solche Dinge bemerkte. Das ist wirklich alles, was ich meinte. Sobald ich die C# -Definition der Funktion geändert habe, um reguläre Skalarparameter zu verwenden, war alles in Ordnung. – EricTheRed

11

Dies ist jetzt möglich. Ich habe eine benutzerdefinierte Modellkonvention erstellt, die die Verwendung von Speicherfunktionen in CodeFirst in EF6.1 ermöglicht. Die Konvention ist in NuGet http://www.nuget.org/packages/EntityFramework.CodeFirstStoreFunctions verfügbar. Hier ist der Link zum Blogpost mit allen Details: http://blog.3d-logic.com/2014/04/09/support-for-store-functions-tvfs-and-stored-procs-in-entity-framework-6-1/

+0

Danke! Ich musste base.OnModelCreating (modelBuilder) zu OnModelCreating hinzufügen, oder Add-Migrations fand viele Änderungen. – Dunc

+1

@Pawel Gibt es einen Grund, warum das mit Npgsql nicht funktionieren würde? –

4

Ich konnte mit dem unten stehenden Code auf TVF zugreifen. Dies funktioniert in EF6. Die Modelleigenschaftsnamen müssen den Datenbankspaltennamen entsprechen.

List<MyModel> data = 
       db.Database.SqlQuery<MyModel>(
       "select * from dbo.my_function(@p1, @p2, @p3)", 
       new SqlParameter("@p1", new System.DateTime(2015,1,1)), 
       new SqlParameter("@p2", new System.DateTime(2015, 8, 1)), 
       new SqlParameter("@p3", 12)) 
      .ToList(); 
+1

Der Code, den Sie gepostet haben, steht nicht einmal mit Entity Framework in Verbindung. – Suamere

+0

@Suamere die Kritik, die Sie anbieten, ist sehr konstruktive und die alternative Antwort, die Sie gepostet zeigen Ihren tiefen Einblick in EF und Programmierung im Allgemeinen, Sie sind wirklich ein "Baws" – user3829854

+0

Ich könnte konstruktiv sein. Einer der Hauptgründe für die Verwendung eines ORM besteht darin, SQL nicht direkt in den Code zu schreiben. Jede Frage zu "Wie mache ich SQL in EF?" Könnte mit "Use SqlQuery" beantwortet werden. Du kannst * alles * auf diese Weise tun. Aber das ist nie die richtige Antwort, denn Sie machen jetzt keine EF mehr. Als ORM wird erwartet, dass es eine Catch-All-SQL-Funktion wie SqlQuery hat, aber niemand erwartet, sie tatsächlich zu verwenden. Wenn diese Antwort akzeptabel ist, warum nicht EF fallen lassen und die 'System.Data' SqlConnection verwenden? Die Antwort lautet: Es ist nicht akzeptabel. Es funktioniert, aber seine schlechte Praxis. – Suamere

1

Ich habe eine Bibliothek für diese Funktionalität entwickelt. Sie können meinen Artikel auf UserTableFunctionCodeFirst überprüfen. Sie können Ihre Funktion verwenden, ohne eine SQL-Abfrage zu schreiben.

aktualisieren

Zunächst einmal Bezug auf die oben genannten Bibliothek hinzufügen und dann haben Sie die Parameterklasse für Ihre Funktion zu erstellen. Diese Klasse kann eine beliebige Anzahl und Art der Parameter

public class TestFunctionParams 
    { 
     [CodeFunctionAttributes.FunctionOrder(1)] 
     [CodeFunctionAttributes.Name("id")] 
     [CodeFunctionAttributes.ParameterType(System.Data.SqlDbType.Int)] 
     public int Id { get; set; } 
    } 

Nun müssen Sie folgende Eigenschaft in Ihrem DbContext hinzufügen Funktion aufrufen und auf die Eigenschaft zuordnen.

[CodeFunctionAttributes.Schema("dbo")] // This is optional as it is set as dbo as default if not provided. 
     [CodeFunctionAttributes.Name("ufn_MyFunction")] // Name of function in database. 
     [CodeFunctionAttributes.ReturnTypes(typeof(Customer))] 
     public TableValueFunction<TestFunctionParams> CustomerFunction { get; set; } 

Dann können Sie Ihre Funktion wie folgt aufrufen.

using (var db = new DataContext()) 
      { 
       var funcParams = new TestFunctionParams() { Id = 1 }; 
       var entity = db.CustomerFunction.ExecuteFunction(funcParams).ToList<Customer>(); 
      } 

Dies wird Ihre benutzerdefinierte Funktion aufrufen und der Entität zuordnen.

7

[Geprüft] mit:

Install-Package EntityFramework.CodeFirstStoreFunctions 

eine Klasse für Ausgabeergebnis erklären:

public class MyCustomObject 
     { 
      [Key] 
      public int Id { get; set; } 
      public int Rank { get; set; } 
     } 

Erstellen Sie eine Methode in Ihrer DbContext Klasse

[DbFunction("MyContextType", "SearchSomething")] 
public virtual IQueryable<MyCustomObject> SearchSomething(string keywords) 
{ 
    var keywordsParam = new ObjectParameter("keywords", typeof(string)) 
          { 
           Value = keywords 
          }; 
    return (this as IObjectContextAdapter).ObjectContext 
    .CreateQuery<MyCustomObject>(
    "MyContextType.SearchSomething(@keywords)", keywordsParam); 
} 

hinzufügen

public DbSet<MyCustomObject> SearchResults { get; set; } 

auf Ihre DbContext Klasse

Fügen Sie in der überschriebenen OnModelCreating Methode:

modelBuilder.Conventions.Add(new FunctionsConvention<MyContextType>("dbo")); 

Und jetzt können Sie rufen/join mit Funktion, um eine Tabellenwerte wie folgt aus:

CREATE FUNCTION SearchSomething 
( 
    @keywords nvarchar(4000) 
) 
RETURNS TABLE 
AS 
RETURN 
(SELECT KEY_TBL.RANK AS Rank, Id 
FROM MyTable 
LEFT JOIN freetexttable(MyTable , ([MyColumn1],[MyColumn2]), @keywords) AS KEY_TBL  
ON MyTable.Id = KEY_TBL.[KEY] 
WHERE KEY_TBL.RANK > 0 
) 
GO 
+0

** NB ** Wenn der Rückgabetyp keine Tabelle ist (also als 'DbSet' hinzufügen und in Migrationen erstellen), fügen Sie ihn als komplexen Typ hinzu. Das '[ComplexType]' - Attribut funktioniert nicht, aber die fließende API tut: in Ihrem Kontexttyp 'OnModelBuilding' add' modelBuilder.ComplexType (); ' – Richard

+0

@Nina Ich habe alle aufgeführten Dinge gemacht, aber immer noch' kann nicht in einen gültigen Typ oder eine gültige Funktion aufgelöst werden. Beinahe Mitgliedzugriffsausdruck'. Wie kann es behoben werden?Ich erwarte es, weil ich Funktion in meine IRepostory-Schnittstelle hinzugefügt habe, anstatt DbContext hinzuzufügen, weil ich keine lokale Funktion zum globalen Kontext hinzufügen möchte. –