Sto cercando di salvare un grafo degli oggetti dei POCO che ho mappato a EF6 usando le notazioni fluenti Code First.
Tuttavia, salvo il grafico dell'oggetto, mi imbatto in eccezioni di violazione della chiave primaria.
Il grafico dell'oggetto è abbastanza semplice:
Un Issue
può contenere più WorkItems
con ciascun Author
(come User
).
Gli oggetti vengono popolati esternamente (utilizzando un'API Web)
Quando tento di salvare un problema con due workitems che si riferiscono allo stesso autore, mi aspetto che il problema venga inserito, i workitems da inserire e un autore da inserire e l'altro da referenziare o da aggiornare.
Tuttavia, ciò che accade è che il problema è inserito, i workitems sono inseriti e vengono inseriti entrambi i riferimenti allo stesso utente, con conseguente violazione della chiave primaria.
Oggetto Issue semplificato:
public class Issue
{
public Issue()
{
WorkItems = new List<WorkItem>();
}
public string Id { get; set; }
private List<WorkItem> _workItems;
public List<WorkItem> WorkItems
{
get { return _workItems ?? new List<WorkItem>(); }
set { _workItems = value; }
}
}
WorkItem semplificato:
public class WorkItem
{
public string Id { get; set; }
public string AuthorLogin
{
get; set;
}
private WorkItemAuthor _author;
public WorkItemAuthor Author
{
get { return _author; }
set { _author = value;
if (value != null)
{
AuthorLogin = value.Login;
}
else
{
AuthorLogin = string.Empty;
}
}
}
}
Oggetto utente semplificato:
public class User
{
public string Login { get; set; }
public string FullName { get; set; }
}
Le loro prime configurazioni del codice:
internal IssueConfiguration()
{
HasKey(x => x.Id);
HasMany(x => x.WorkItems);
}
internal WorkItemConfiguration()
{
HasKey(x => x.Id);
HasRequired(p => p.Author)
.WithMany(b => b.WorkItems)
.HasForeignKey(x=>x.AuthorLogin);
}
internal UsersConfiguration()
{
HasKey(x => x.Login);
}
Tutto abbastanza semplice. Al momento della creazione del database, le tabelle sembrano belle e dandy, con FK sulle colonne dove ci si aspetterebbe
Ora, quando si salvava il problema, sarebbe stato bello se il grafico dell'oggetto fosse inserito, e il riferimento agli oggetti esistenti sarebbe stato riconosciuto automaticamente e facoltativamente inserito o referenziato.
Cerco di aggiungere problemi di conseguenza:
using (var db = new Cache.Context())
{
if (db.Issues.Any(e => e.Id == issue.Id))
{
db.Issues.Attach(issue);
db.Entry(issue).State = EntityState.Modified;
}
else
{
db.Issues.Add(issue);
}
db.SaveChanges();
}
La soluzione a questo problema è che passo attraverso il grafico degli oggetti per aggiungere o allegare manualmente gli altri oggetti nel grafico? Mi aspetterei definendo i valori appropriati di chiave esterna che questi riferimenti sarebbero riconosciuti.
Alla fine ho finito per fare qualcosa di simile a questo, abbastanza laborioso e mi piacerebbe ancora trovare un modo migliore. Scoprire se un'entità è già collegata o esiste nel database risulta essere inquinante del modello troppo (l'implementazione di IEquatable<T>
va bene, ma penso che l'implementazione di IEntityWithKey
sui miei POCO inquini troppo il POCO. non sembrano sufficienti entità di tracciamento nel contesto)
internal static void Save(this List<Issue> issues)
{
using (var db = new Context())
{
foreach (var issue in issues.ToList())
{
foreach (var workItem in issue.WorkItems.ToList())
{
if (workItem.Author != null)
{
var existing = db.Users.SingleOrDefault(e => e.Login == workItem.Author.Login);
if (existing == null)
{
db.Users.Add(workItem.Author);
}
else
{
//Update existing entities' properties
existing.Url = workItem.Author.Url;
//Replace reference
workItem.Author = existing;
}
db.SaveChanges();
}
var existingWorkItem = db.WorkItems.SingleOrDefault(e => e.Id == workItem.Id);
if (existingWorkItem == null)
{
db.WorkItems.Add(workItem);
}
else
{
//Update existing entities' properties
existingWorkItem.Duration = workItem.Duration;
//Replace reference
issue.WorkItems.Remove(workItem);
issue.WorkItems.Add(existingWorkItem);
}
db.SaveChanges();
}
var existingIssue = db.Issues.SingleOrDefault(x => x.Id == issue.Id);
if (existingIssue == null)
{
db.Issues.Add(issue);
}
else
{
//Update existing entities' properties
existingIssue.SpentTime = issue.SpentTime;
}
db.SaveChanges();
}
}
}
C'è un piccolo bug nell'oggetto Issue.
"return _workItems ?? new List ();" potrebbe restituire un nuovo oggetto di lavoro su ogni get if _workItems è diventato null. Ecco la versione fissa.
public class Issue {
public Issue() {
WorkItems = new List<WorkItem>();
}
public String Id {
get; set;
}
public List<WorkItem> WorkItems { get; private set; }
}