Il contesto è ogni Car
ha un CarBrand
corrispondente. Ora le mie classi sono come mostrato di seguito:
public class Car
{
public int CarId { get; set; }
public int CarBrandId { get; set; }
public CarBrand CarBrand { get; set; }
}
public class CarBrand
{
public int CarBrandId { get; set; }
public string Name { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Car> Cars { get; set; }
public DbSet<CarBrand> CarBrands { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(@"Data Source = MyDatabase.sqlite");
}
}
Ecco un esempio di esecuzione del mio codice ...
class Program
{
static void Main(string[] args)
{
AlwaysCreateNewDatabase();
//1st transaction
using (var context = new MyContext())
{
var honda = new CarBrand() { Name = "Honda" };
var car1 = new Car() { CarBrand = honda };
context.Cars.Add(car1);
context.SaveChanges();
}
//2nd transaction
using (var context = new MyContext())
{
var honda = GetCarBrand(1);
var car2 = new Car() { CarBrand = honda };
context.Cars.Add(car2);
context.SaveChanges(); // exception happens here...
}
}
static void AlwaysCreateNewDatabase()
{
using (var context = new MyContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
}
}
static CarBrand GetCarBrand(int Id)
{
using (var context = new MyContext())
{
return context.CarBrands.Find(Id);
}
}
}
Il problema è che ottengo l'eccezione 'UNIQUE constraint failed: CarBrands.CarBrandId' quando car2
viene aggiunto al database con lo stesso CarBrand
honda
.
Quello che mi aspetto che faccia sia durante il context.SaveChanges()
della seconda transazione.SaveChanges context.SaveChanges()
, aggiungerà car2
e imposterà la sua relazione con CarBrand
appropriata, ma ricevo invece un'eccezione.
EDIT: ho davvero bisogno di ottenere la mia istanza CarBrand in un contesto / transazione diversa.
//really need to get CarBrand instance from different context/transaction
CarBrand hondaDb = null;
using (var context = new MyContext())
{
hondaDb = context.CarBrands.First(x => x.Name == "Honda");
}
//2nd transaction
using (var context = new MyContext())
{
var car2 = new Car() { CarBrand = hondaDb };
context.Cars.Add(car2);
context.SaveChanges(); // exception happens here...
}
Il problema è che il metodo Add
è in cascata:
Inizia a tracciare l'entità data e tutte le altre entità raggiungibili che non sono già state rintracciate , nello stato
Added
tale che vengano inserite nel database quando viene chiamatoSaveChanges
.
Esistono molti modi per raggiungere l'obiettivo, ma il più flessibile (e suppongo che il preferito) sia sostituire la chiamata del metodo Add
con il metodo ChangeTracker.TrackGraph
:
Inizia a tracciare un'entità e tutte le entità che sono raggiungibili attraversandone le proprietà di navigazione. Traversal è ricorsivo, quindi verranno analizzate anche le proprietà di navigazione di tutte le entità scoperte. Il callback specificato viene chiamato per ciascuna entità rilevata e deve impostare lo Stato in cui ciascuna entità deve essere tracciata. Se non viene impostato nessuno stato, l'entità rimane non tracciata. Questo metodo è progettato per l'uso in scenari disconnessi in cui le entità vengono recuperate utilizzando un'istanza del contesto e quindi le modifiche vengono salvate utilizzando un'istanza diversa del contesto. Un esempio di ciò è un servizio Web in cui una chiamata di servizio recupera le entità dal database e un'altra chiamata di servizio persiste qualsiasi modifica alle entità. Ogni chiamata di servizio utilizza una nuova istanza del contesto che viene eliminata al termine della chiamata. Se viene rilevata un'entità che è già tracciata dal contesto, quell'entità non viene elaborata (e le sue proprietà di navigazione non vengono attraversate).
Quindi, invece di context.Cars.Add(car2);
potresti usare quanto segue (è piuttosto generico e dovrebbe funzionare in quasi tutti gli scenari):
context.ChangeTracker.TrackGraph(car2, node =>
node.Entry.State = !node.Entry.IsKeySet ? EntityState.Added : EntityState.Unchanged);