Danke für die Hinweise! Ich habe sie angesehen und mir die Adressen notiert, mehr aber noch nicht. Ausprobieren wird Zeit brauchen und wahrscheinlich habe ich da noch einiges F# dazu zu lernen. Mal sehen, ob sich weitere Fragen ergeben oder ob ich zurechtkomme.
Ich würde gern WPF-Anwendungen schreiben, die kein (oder wenig) C# enthalten, sondern F# für die eigentliche Arbeit verwenden. Und ich habe Schwierigkeiten, dafür Informationen zu finden. Ich bin auf ElmishWPF gestoßen, aber das scheint wohl schon länger nicht mehr weiter entwickelt zu werden. Jedenfalls schien mir das entsprechende NuGet-Package mit meiner .NET-Version (.NET 8.0 als neuestes und standardmäßig vorgegebenes, in Visual Studio 2022) nicht zusammenzupassen.
Ein F#-Forum kann ich nicht finden, die Fragen auf StackOverflow waren überwiegend recht alt und haben auch sonst nicht gut gepasst.
Noch was: es geht um Programme, die ich selbst benutze (und niemand sonst), und die eher klein sind. Meist mit Anbindung an ebenfalls kleine Datenbanken.
Nochmalige Suche in der EF-Dokumentation hat wohl die entscheidende Stelle zutage gefördert:
Note that DbSet and IDbSet always create queries against the database and will always involve a round trip to the database even if the entities returned already exist in the context. A query is executed against the database when:
It is enumerated by a foreach (C#) or For Each (Visual Basic) statement. It is enumerated by a collection operation such as ToArray, ToDictionary, or ToListenter link description here. LINQ operators such as First or Any are specified in the outermost part of the query. The following methods are called: the Load extension method on a DbSet, DbEntityEntry.Reload, and Database.ExecuteSqlCommand.
When results are returned from the database, objects that do not exist in the context are attached to the context. If an object is already in the context, the existing object is returned (the current and original values of the object's properties in the entry are not overwritten with database values). Finding entities using a query
Das dürfte der Knackpunkt sein. Es widerspricht im Übrigen auch meinem alten Lehrbuch nicht, wird dort halt nur nicht erwähnt (an der Stelle liegt der Fokus auf dem Vermeiden unnötiger Datenbankabfragen).
So dass ich also eine der von @Marco_GR aufgezählten Varianten brauche (Danke schön!). Damit scheint mir das Problem gelöst, ich habe den Thread als erledigt markiert.
Sending raw commands to the database
Note that any changes made to data in the database using ExecuteSqlCommand are opaque to the context until entities are loaded or reloaded from the database.
Ein Kontext kann einfach nicht das tracken, was Du an ihm vorbei schleust.
Das ist mir nicht neu, aber:
So far you’ve used LINQ to query a DbSet directly, which always results in a SQL query being sent to the database to load the data. Quelle: Lerman, Julia; Miller, Rowan: Programming Entity Framework : DbContext. O'Reilly Media, 2012. Kap. 2: Querying with DbContext; Querying Local Data. Blöderweise finde ich in meiner EBook-Ausgabe keine Seitenzahl dafür.
Daraus schließe ich doch, dass die Abfrage nach der Ausführung des SQL-Kommandos die Entities eben gerade neu aus der Datenbank lädt.
Warum Du einen ORM verwendest, um dann SQL Code Raw zu senden, erschließt sich mir nicht.
Das Verhalten ist - so wie ich das sehen - genau das.
So weit ich sehen kann, ist das die einzige Möglichkeit, aus dem EF heraus die Ausführung einer gespeicherten Prozedur anzustoßen. Und an einer Stelle in meinem Programm soll eben das stattfinden.
verwendetes Datenbanksystem: PostgreSQL 10.2, .NET-Provider Npgsql
Hallo,
ich möchte in einem WPF-Programm folgendes machen:
Für den ganzen Vorgang wird eine Instanz der passenden von DbContext abgeleiteten Klasse benutzt, die beim Öffnen des Fensters erzeugt und beim Schließen zerstört wird.
Das funktioniert so nicht: nach der zweiten Abfrage werden im DataGrid die unveränderten Daten angezeigt. Ich kann mich aber davon überzeugen, dass die gespeicherte Prozedur richtig ausgeführt wurde, z.B. indem ich das Fenster schließe und neu öffne.
Also habe ich ein kleines Konsolenprogramm gebastelt, das etwas Ähnliches tut:
Tut richtig, wenn die drei Aktionen in getrennten DbContext-Instanzen ablaufen; ich nehme sehr stark an, dass ich die ersten beiden auch zusammenfassen könnte, habe das aber noch nicht probiert. Läuft alles im gleichen Kontext ab, dann hat die zweite Abfrage das gleiche Ergebnis wie die erste.
Fragen, die ich mit Hilfe der Dokumentation nicht lösen konnte:
Was genau passiert da eigentlich? Ich lese immer und überall, dass Abfragen an den DbContext an die Datenbank geschickt werden - wenn man es anders haben will, muss man das extra einrichten. Auch wenn der SQL-Befehl in seiner eigenen Transaktion ausgeführt wird, müsste eine Abfrage, die danach abgesetzt wird, doch das Ergebnis schon kennen? Oder wird vielleicht der SQL-Befehl ohne mein ausdrückliches Zutun in einem eigenen Thread ausgeführt und das "danach" stimmt so eben nicht?
Ist eine neue DbContext-Instanz das Mittel der Wahl oder geht es auch anders?
Ich hänge das Konsolenprogramm mal an, obwohl der Beitrag sowieso schon lang ist. Es benutzt eine PostgreSQL-Version der Northwind-Datenbank, daraus nur drei Felder der Tabelle "Customers". Die zwei getrennten Namensräume sind hier natürlich ein schlechter Witz.
Danke für Hinweise aller Art!
#######################################################################
//Customer.cs
using System;
namespace Nordwind_Daten
{
public class Customer
{
public string CustomerId { get; set; }
public string CompanyName { get; set; }
public string Region { get; set; }
public override string ToString() => String.Format("Customer Id {0}, Company {1}, Region {2}",
CustomerId, CompanyName, Region);
}
}
//CustomerMap.cs
using System.Data.Entity.ModelConfiguration;
namespace Nordwind_Daten
{
internal class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
ToTable("customers");
Property(c => c.CustomerId).HasColumnName("customerid")
.IsFixedLength().HasMaxLength(5).IsRequired();
Property(c => c.CompanyName).HasColumnName("companyname")
.HasMaxLength(40).IsRequired();
Property(c => c.Region).HasColumnName("region").HasMaxLength(15);
}
}
}
//NordwindDB.cs
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace Nordwind_Daten
{
public class NordwindDB : DbContext
{
static NordwindDB()
{
Database.SetInitializer<NordwindDB>(null);
}
public NordwindDB() : base("name=NWConn")
{ }
public DbSet<Customer> CustomerSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.HasDefaultSchema("public");
modelBuilder.Configurations.Add(new CustomerMap());
}
}
}
//Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Npgsql;
using NpgsqlTypes;
using Nordwind_Daten;
namespace ExecSql_Nordwind
{
class Program
{
static string GetCustomersByIdStart(string idstart, NordwindDB CDB)
{
var sb = new StringBuilder();
//using (var CDB = new NordwindDB())
//{
var query = from cust in CDB.CustomerSet
where cust.CustomerId.StartsWith(idstart)
orderby cust.CustomerId
select cust;
foreach (var cust in query)
{
sb.AppendLine(cust.ToString());
}
//}
return sb.ToString();
}
static int ChangeRegion(string idstart, string newchar, NordwindDB CDB)
{
string sql = "UPDATE customers SET region = CONCAT(region, :newchar) " +
"WHERE customerid LIKE :idstart";
//using (var CDB = new NordwindDB())
//{
var par_nc = new NpgsqlParameter(":newchar", NpgsqlDbType.Char, 1);
var par_ids = new NpgsqlParameter(":idstart", NpgsqlDbType.Char, 2);
par_nc.Value = newchar;
par_ids.Value = idstart + "%";
int cnt = CDB.Database.ExecuteSqlCommand(sql, par_nc, par_ids);
return cnt;
//}
}
static void Main(string[] args)
{
string newchar = "*";
string appchar = "C";
using (var CDB = new NordwindDB())
{
Console.WriteLine(GetCustomersByIdStart(appchar, CDB));
int z = ChangeRegion(appchar, newchar, CDB);
Console.WriteLine("Ergebnis von ChangeRegion: {0}", z);
Console.WriteLine(GetCustomersByIdStart(appchar, CDB));
}
Console.Write("Beenden mit beliebiger Taste ...");
Console.ReadKey();
}
}
}
Das ging ja fix! Danke an alle Antwortenden.
bzgl. der Exception. Dies liegt daran, daß bei der ObservableCollection<T> die virtuelle Methode InsertItem überschrieben ist
Und daher führt ((List)Items).AddRange(ships) keine PropertyChanged/CollectionChanged-Ereignisse aus, welches wiederum für die Konsistenz nötig sind.
Danke für die Klärung! Dann habe ich also die Wahl zwischen einer Schleife mit Add-Aufrufen und einer neuen Instanz. Na schön.
@T-Virus: Ich habe die eigene Subklasse ShipsCollection nur erstellt, um an die Items-Eigenschaft heranzukommen, die ist ja protected. Aber nachdem das nicht tut, was ich mir davon erhofft habe, ist die Klasse nicht mehr nötig.
Aufgabe: Der Inhalt einer ObservableCollection soll auf Kommando komplett ausgetauscht werden (meist als Ergebnis einer Suche in einem größeren Datenbestand).
Das funktioniert, wenn die neuen Objekte der Liste einzeln zugefügt werden:
public void Refill(IEnumerable<Ship> ships)
{
Clear();
foreach (var ship in ships)
{
Add(ship);
}
}
Eigentlich läge es doch aber nahe, die neuen Objekte in einem Rutsch zuzufügen, mit AddRange oder so. Die ObservableCollection hat allerdings keine Methode, die das tut, ich habe in der Dokumentation jedenfalls nichts dergleichen gefunden. Versucht habe ich dies hier:
public class ShipsCollection : ObservableCollection<Ship>
{
public ShipsCollection() : base() { }
public ShipsCollection(IEnumerable<Ship> ships) => Refill(ships);
public void Refill(IEnumerable<Ship> ships)
{
Clear();
((List)Items).AddRange(ships);
}
}
Das wird zwar anstandslos kompiliert, das Programm startet auch und das Ergebnis der ersten Suche wird richtig angezeigt. So bald aber eine neue Suche ein anderes Ergebnis hat, gibt es eine Exception. Weil der interessante Teil des Textes dazu recht lang ist, stelle ich erst mal meine Frage und hänge ihn danach an:
Stimmt meine Vermutung, dass so ein Komplett-Austausch der Elemente einer ObservableCollection nicht möglich ist oder habe ich nur den richtigen Weg nicht gefunden?
Klar ist, dass ich eine neue Instanz der Collection mit neuem Inhalt erzeugen könnte, aber dann müsste ich INotifyPropertyChanged auf die Collection als Ganzes anwenden. Und wäre das überhaupt eine gute Idee?
Und hier der gekürzte Text zur Exception:
Fehlermeldung:
System.InvalidOperationException ist aufgetreten.
HResult=0x80131509
Nachricht = Ein ItemsControl ist nicht konsistent mit seiner Elementquelle.
Weitere Informationen finden Sie in der inneren Ausnahme.
Quelle = <Die Ausnahmequelle kann nicht ausgewertet werden.>
...
Die Ausnahme wurde ausgelöst, da der Generator für Steuerelement 'System.Windows.Controls.DataGrid Items.Count:5' mit dem Namen '(unbenannt)' eine Reihe von CollectionChanged-Ereignissen empfangen hat, die nicht mit dem aktuellen Status der Elementsammlung übereinstimmen. Die folgenden Unterschiede wurden festgestellt:
Gesammelte Anzahl 1 unterscheidet sich von der tatsächlichen Anzahl 5. [Gesammelte Anzahl ist (Anzahl bei letztem Reset + #Adds - #Removes seit letztem Reset).]Eine oder mehrere der folgenden Quellen haben möglicherweise falsche Ereignisse ausgelöst:
System.Windows.Controls.ItemContainerGenerator
System.Windows.Controls.ItemCollection
System.Windows.Data.ListCollectionView
ShipsCrud.ShipsCollection
(Die beteiligten Quellen werden als die wahrscheinlichere Ursache des Problems betrachtet.)
Die häufigsten Ursachen umfassen (a) das Ändern der Sammlung oder deren Anzahl ohne Auslösen eines entsprechenden Ereignisses sowie (b) das Auslösen eines Ereignisses mit falschem Index- oder Elementparameter.
Aber dass deine beiden Queries im Startpost syntaktische Fehler haben, hast du gelesen?
Vielleicht bin ich vollkommen blind, aber ich verstehe nicht, wieso es beim ersten query funktioniert, beim zweiten nicht^^
Da würde ich mich jetzt gern anhängen: ich dachte eigentlich, ich wüsste, was an den beiden Abfragen auf genau die gleiche Weise falsch ist, aber warum Du bei der ersten richtige Ergebnisse (oder überhaupt ein Ergebnis) bekommen hast, bleibt mir ein Rätsel.
Der "DB Browser for SQLite" teilt mir bei einer vergleichbaren Fehlkonstruktion sofort mit, dass hier ein Syntaxfehler vorliegt. Ebenso sqlite3.exe. Dasselbe würde ich eigentlich von jedem derartigen Programm erwarten.
Ich weiß, der Thread ist nicht mehr druckfrisch, aber im Hinblick auf spätere Leser:
Du übergibst in keinem Fall eine Methode als Parameter; die hätte einen ganz anderen Datentyp. Du übergibst jedesmal das Ergebnis eines Methodenaufrufs, nur gibst Du dem einmal einen eigenen Namen, einmal nicht. Die Vorteile des ersten Vorgehens wurden schon genannt.
Wenn das Feld Id heissen würde, würde das EF automatisch erkennen, dass es sich hier um den Key handelt und ein Auto-Increment erfolgt.
PersonId erkennt das Ef nicht automatisch.
Das würde mich in der Klasse Person doch arg wundern. Die von Dir genannte Quelle sagt jedenfalls bei den Konventionen (Hervorhebung von mir):
The default convention for primary key is that Code-First would create a primary key for a property if the property name is Id or <class name>Id (NOT case sensitive). (Entity Framework Code First Conventions