In einem früheren Artikel habe ich mehrere Probleme vorgestellt, die bei der Verwendung von LINQ to SQL für Aktualisierungsvorgänge auftreten können. Tatsächlich ist dies kein Problem, auf das ich allein gestoßen bin. Als ich im Internet nach Antworten suchte, stellte ich fest, dass viele Leute ähnliche Artikel zu diesem Thema veröffentlicht hatten. Womit ich jedoch nicht zufrieden bin, ist, dass sie zwar das Problem angesprochen, aber keine detaillierte Analyse durchgeführt haben. Sie haben nur Lösungen gegeben (z. B. das Hinzufügen von RowVersion-Spalten, das Entfernen von Zuordnungen usw.), aber nicht erklärt, warum sie dies tun müssen . Dies ist auch die ursprüngliche Absicht beim Schreiben des vorherigen Artikels. Ich hoffe, durch die Analyse des LINQ to SQL-Quellcodes Schritt für Schritt die Lösung des Problems herauszufinden. In diesem Artikel werden diese Methoden einzeln erläutert.
Option 1: Neuzuweisung Im Open-Source-Framework Ezsocio von TerryLee, Anytao, Ding Xue und anderen wird an einigen Stellen eine Neuzuweisung übernommen. Rufen Sie innerhalb der Update-Methode die Entitäten in der Datenbank basierend auf dem Primärschlüssel ab und weisen Sie dann ihren Eigenschaften nacheinander Werte mit den Entitäten in den Parametern zu.
public void UpdateProfile(Profile p)
{
using (RepositoryContext db = new RepositoryContext())
{
var Profile = db.GetTable<Profile>().First<Profile>(u => u.ID == p.ID);
Profile.Birthday = p.Birthday;
Profile.Gender = p.Gender;
Profile.Hometown = p.Hometown;
Profil.MSN = p.MSN;
Profile.NickName = p.NickName;
Profile.PhoneNumber = p.PhoneNumber;
Profil.QQ = p.QQ;
Profile.State = p.State;
Profile.TrueName = p.TrueName;
Profile.StateRefreshTime = p.StateRefreshTime;
Profile.Avatar = p.Avatar;
Profile.Website = p.Website;
db.SubmitChanges();
}
}
Bruder Yang Guo stellte für diese Lösung auch eine Reflexionsmethode bereit, um ein automatisches Kopieren von Attributwerten zu erreichen.
Ich persönlich denke jedoch, dass dies ein Schema ist, das die Realität vermeidet und die Realität vermeidet. Es verwendet nicht die von LINQ to SQL bereitgestellte API für Aktualisierungsvorgänge, sondern verfolgt eine Umwegstrategie. Dies ist tatsächlich ein Kompromiss. Liegt es daran, dass die Attach-Methode „nicht einfach zu verwenden“ ist und wir sie nicht verwenden? hehe.
Option 2: Deaktivieren Sie die Objektverfolgung. In diesem Zusammenhang schlug lea vor, dass die korrekte Aktualisierung erreicht werden kann, indem die ObjectTrackingEnabled-Eigenschaft von DataContext auf „false“ gesetzt wird.
öffentliches Produkt GetProduct(int id)
{
NorthwindDataContext db = new NorthwindDataContext();
db.ObjectTrackingEnabled = false;
return db.Products.SingleOrDefault(p => p.ProductID == id);
}
Keine weiteren Codeänderungen.
Warum kann es nach der Deaktivierung der Objektverfolgung normal aktualisiert werden? Lassen Sie uns die Antwort aus dem Quellcode finden.
öffentlicher bool ObjectTrackingEnabled
{
erhalten
{
this.CheckDispose();
return this.objectTrackingEnabled;
}
Satz
{
this.CheckDispose();
if (this.Services.HasCachedObjects)
{
throw System.Data.Linq.Error.OptionsCannotBeModifiedAfterQuery();
}
this.objectTrackingEnabled = value;
if (!this.objectTrackingEnabled)
{
this.deferredLoadingEnabled = false;
}
this.services.ResetServices();
}
}
Es stellt sich heraus, dass, wenn ObjectTrackingEnabled auf „false“ gesetzt ist, DeferredLoadingEnabled gleichzeitig auf „false“ gesetzt wird. Auf diese Weise werden beim Ausführen der Abfrage keine Daten für die Entität geladen, die eine verzögerte Abfrage erfordern, sodass beim Anhängen keine Ausnahme ausgelöst wird (siehe Analyse im vorherigen Artikel).
In MSDN erhalten wir außerdem die folgenden nützlichen Informationen: Das Festlegen der ObjectTrackingEnable-Eigenschaft auf „false“ kann die Abrufleistung verbessern, da dadurch die Anzahl der zu verfolgenden Elemente reduziert wird. Dies ist eine sehr verlockende Funktion.
Bei der Deaktivierung der Objektverfolgung sollte jedoch besonders auf zwei Punkte geachtet werden: (1) Sie muss vor der Ausführung der Abfrage deaktiviert werden. (2) Nach der Deaktivierung können die Methoden Attach und SubmitChanges nicht mehr aufgerufen werden. Andernfalls wird eine Ausnahme ausgelöst.
Option 3: Entfernen Sie die Zuordnung. Im vorherigen Artikel wurde eine lahme Methode vorgestellt, die darin besteht, die mit dem Produkt verknüpfte Kategorie in der GetProduct-Methode manuell auf Null zu setzen. Wir können diesen Teil des Codes extrahieren und in eine Detach-Methode einfügen. Da es sich bei dieser Detach-Methode um eine Entitätsmethode handelt, können Teilklassen verwendet werden:
öffentliche Teilklasse Produkt
{
public void Detach()
{
this._Category = default(EntityRef<Category>);
}
}
öffentliche Teilklasse Kategorie
{
public void Detach()
{
foreach (var Produkt in this.Products)
{
Produkt.Detach();
}
}
}Aber diese Methode, Detach für jede Entität zu definieren, ist zu umständlich. Mit zunehmender Anzahl an Entitäten werden die Beziehungen immer komplexer und es kommt leicht zu fehlenden Attributen. Zhang Yi schlug eine sehr elegante Methode vor, um diese Logik mithilfe von Reflexion zu abstrahieren:
private void Detach(TEntity-Entität)
{
foreach (FieldInfo fi in entity.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{
if (fi.FieldType.ToString().Contains("EntityRef"))
{
var value = fi.GetValue(entity);
if (Wert != null)
{
fi.SetValue(entity, null);
}
}
if (fi.FieldType.ToString().Contains("EntitySet"))
{
var value = fi.GetValue(entity);
if (Wert != null)
{
MethodInfo mi = value.GetType().GetMethod("Clear");
if (mi != null)
{
mi.Invoke(value, null);
}
fi.SetValue(entity, value);
}
}
}
}
Einige Leute sind auch der Meinung, dass die Ereignisse PropertyChanging und PropertyChanged beim Trennen auf Null gesetzt werden sollten, aber die Grundidee ist dieselbe.
Option 4: Delegation verwenden. Dies ist die von ZC29 in den Kommentaren meines letzten Artikels beschriebene Methode.
public void UpdateProductWithDelegate(Expression<Func<Product, bool>> predicate, Action<Product> action)
{
NorthwindDataContext db = new NorthwindDataContext();
var product = db.Products.SingleOrDefault(predicate);
Aktion(Produkt);
db.SubmitChanges();
}
//Client-Code
ProductRepository-Repository = new ProductRepository();
Repository.UpdateProductWithDelegate(p => p.ProductID == 1, p =>
{
p.ProductName = "Geändert";
});
Verwenden Sie Lambda-Ausdrücke, um die GetProduct-Logik in UpdateProduct einzubetten, und verwenden Sie Delegaten, um die Ausführung der Aktualisierungslogik zu verzögern. Dadurch werden Suche und Aktualisierung geschickt in einen DataContext eingefügt und so Attach umgangen. Allerdings ist die API dieser Methode etwas zu komplex und erfordert zu viele Client-Programmierer. Darüber hinaus muss die Get-Logik im Update erneut ausgeführt werden. Obwohl der Leistungsverlust minimal ist, scheint es den Leuten immer das Gefühl zu geben, dass sie nicht trocken genug ist.
Option 5: Verwenden Sie die UPDATE-Anweisung. Im Quellcode von Ezsocio habe ich die Methode RepositoryBase.UpdateEntity gefunden. Das Zusammenfügen von SQL-Anweisungen erfolgt innerhalb der Methode und nur die geänderten Spalten werden aktualisiert. Da ITable hier nicht mehr verwendet wird und eine vollständige Framework-Unterstützung erforderlich ist, wird auf weitere Kommentare verzichtet. Weitere Informationen finden Sie im Quellcode von Ezsocio.
Zusammenfassung Dieser Artikel listet mehrere Lösungen auf, die ich in den letzten Tagen im Internet gefunden habe. Sie alle haben Vor- und Nachteile. Welche besser oder schlechter ist, hängt von den Meinungen verschiedener Personen ab. Im nächsten Artikel werde ich die Leistung dieser Methoden vergleichen, um die optimale Lösung zu finden.