2016-03-23 19 views
6

Sto provando a eseguire un Upsert di un oggetto Advertisement che contiene uno List<AdImage>. Advertisement contiene una chiave esterna corrispondente a User. A user può avere zero o più Advertisements e uno Advertisement ha uno o più AdImages.Come specificare un ordine valido per le operazioni dipendenti quando si definiscono le relazioni di entità?

L'upsert riesce con il seguente: Si è verificato

un errore durante il salvataggio entità che non espongono stranieri chiave proprietà per le loro relazioni. La proprietà EntityEntries restituirà il valore restituendo null poiché non è possibile identificare una singola entità come origine dell'eccezione. La gestione delle eccezioni durante il salvataggio può essere semplificata con esponendo le proprietà delle chiavi esterne nei tipi di entità. Vedi InnerException per i dettagli.

Dove eccettuati interna è:

impossibile determinare un ordinamento valido per le operazioni di carico. Le dipendenze possono esistere a causa di vincoli di chiavi esterne, dei requisiti del modello o di valori generati dallo store.

Il advertisement viene creata un'istanza molto semplicemente:

var ad = new Advertisement 
{ 
    AdImages = new List<AdImage> 
    { 
     new AdImage {Image = model.Image} 
    }, 

    Message = model.Message, 
    Title = model.Title, 
    User = user, 
}; 

_aAdAppService.UpsertAdvertisement(ad); 

I soggetti in questione sono definiti come:

public class User : AbpUser<Tenant, User> 
{ // AbpUser is a 3rd party class which defines Id as a primary key 

    public string AccessToken { get; set; } 
    public long UserId { get; set; } 

    public virtual List<Advertisement> Advertisements { get; set; } 
} 

public class Advertisement : Entity 
{ 
    [Key] 
    public long Id { get; set; } 

    public string Title { get; set; } 
    public string Message { get; set; } 
    public List<AdImage> AdImages { get; set; } 

    public virtual User User { get; set; } 
} 

public class AdImage : Entity 
{ 
    [Key] 
    public int Id { get; set; } 
    public string Image { get; set; } 

    public virtual Advertisement Advertisement { get; set; } 
} 

questo modo le relazioni sono definite:

protected override void OnModelCreating(DbModelBuilder modelBuilder) 
{ 
    modelBuilder.Entity<User>() 
     .HasMany(u => u.Advertisements) 
     .WithRequired(x => x.User); 

    modelBuilder.Entity<Advertisement>() 
     .HasMany(a => a.AdImages) 
     .WithRequired(x => x.Advertisement); 

    modelBuilder.Entity<AdImage>() 
     .HasRequired(x => x.Advertisement); 

    base.OnModelCreating(modelBuilder); 

} 

Che cosa significa l'errore sage mean? Non riesco a vedere come i miei rapporti siano definiti in modo errato. Come posso risolvere questo?

+0

Basta guardarlo e questo può essere uno sparo nel buio. Hai provato a cambiare pubblico Elenco AdImages {get; set;} nel modello Advertisement a una proprietà di navigazione EF: public virtual ICollection AdImages {get; impostato;} ? – JDupont

+1

Hmm, non so se sarebbe di aiuto, ma prova a rimuovere 'modelBuilder.Entity () .HasRequired (x => x.Advertisement);', la relazione è già configurata correttamente dall'istruzione precedente, e questo potrebbe sovrascriverlo in modo errato dal momento che è incompleto. –

risposta

1

La soluzione a questo era di avvolgere il mio endpoint in un UnitOfWork. Non sono abbastanza esperto nelle Scritture di Entity Framework per descrivere esattamente il problema o perché questo ha funzionato, ma è successo.

Questo è un esempio di ciò che ha funzionato (anche se leggermente differente dal codice dell'esempio precedente):

[UnitOfWork] 
public async void Post(AdvertisementVM model) 
{ 
    CheckModelState(); 

    try 
    { 
     if (_unitOfWorkManager.Current == null) 
     { 
      using (var mgr = _unitOfWorkManager.Begin()) 
      { 
       await ExecuteMultipleDatabaseCalls(model); 

       await mgr.CompleteAsync(); 
      } 
     } 
     else 
     { 
      await ExecuteMultipleDatabaseCalls(model); 
     } 
    } 
    catch (Exception ex) 
    { 
     throw new HttpException((int)HttpStatusCode.InternalServerError, ex.Message); 
    } 
} 

private async Task ExecuteMultipleDatabaseCalls(AdvertisementVM model) 
{ 
    var retailer = _retailerAppService.GetForUser(model.UserId); 

    var ad = new Advertisement 
    { 
     Message = model.Message, 
     Title = model.Title, 

     Retailer = retailer 
    }; 

    await _adAppService.InsertOrUpdate(ad); 

    await _unitOfWorkManager.Current.SaveChangesAsync(); 
} 

L'attributo UnitOfWork è un membro del progetto ASP.NET Boilerplate, ed è definito come segue:

Sommario: Questo attributo viene utilizzato per indicare che il metodo di dichiarazione è atomico e deve essere considerato come un'unità di lavoro. Un metodo con questo attributo intercettato, una connessione al database viene aperta e una transazione viene avviata prima di chiamare il metodo. Alla fine della chiamata al metodo, la transazione viene confermata e tutte le modifiche vengono applicate al database se non vi sono eccezioni, altrimenti viene eseguito il rollback.

Osservazione: Questo attributo non ha effetto se c'è già un'unità di lavoro prima di chiamare questo metodo, in caso affermativo, utilizza la stessa transazione.

1

Questo è solo un commento, ma non riesco a scriverlo nei commenti ...

E 'la prima volta che vedo l'eccezione

Impossibile determinare un ordinamento valido per le operazioni a carico. Le dipendenze possono esistere a causa di vincoli di chiavi esterne, dei requisiti del modello o dei valori generati dal negozio.

così ho provato a riprodurlo.

Questo è il modo in cui ho implementato le lezioni perse e il contesto

public class Entity 
{} 

public class Tenant 
{} 

public class AbpUser<T1, T2> 
{} 



public Context(DbConnection connection) 
    : base(connection, false) 
{ 
} 

public DbSet<Advertisement> Advertisements { get; set; } 
public DbSet<User> Users { get; set; } 
public DbSet<AdImage> AdImages { get; set; } 

protected override void OnModelCreating(DbModelBuilder modelBuilder) 
{ 
    modelBuilder.Entity<User>() 
     .HasMany(u => u.Advertisements) 
     .WithRequired(x => x.User); 

    modelBuilder.Entity<Advertisement>() 
     .HasMany(a => a.AdImages) 
     .WithRequired(x => x.Advertisement); 

    modelBuilder.Entity<AdImage>() 
     .HasRequired(x => x.Advertisement); 

    base.OnModelCreating(modelBuilder); 

} 

Questo sono le istruzioni DDL generati da EF durante la migrazione automatica

ExecuteNonQuery========== 
CREATE TABLE [AdImages] (
[Id] int not null identity(1,1) 
, [Image] text null 
, [Advertisement_Id] int not null 
); 
ALTER TABLE [AdImages] ADD CONSTRAINT [PK_AdImages_87d4bad2] PRIMARY KEY ([Id]) 
ExecuteNonQuery========== 
CREATE TABLE [Advertisements] (
[Id] int not null identity(1,1) 
, [Title] text null 
, [Message] text null 
, [User_UserId] int not null 
); 
ALTER TABLE [Advertisements] ADD CONSTRAINT [PK_Advertisements_5d578c9a] PRIMARY KEY ([Id]) 
ExecuteNonQuery========== 
CREATE TABLE [Users] (
[UserId] int not null identity(1,1) 
, [AccessToken] text null 
); 
ALTER TABLE [Users] ADD CONSTRAINT [PK_Users_5d578c9a] PRIMARY KEY ([UserId]) 
ExecuteNonQuery========== 
CREATE INDEX [IX_Advertisement_Id] ON [AdImages] ([Advertisement_Id]) 
ExecuteNonQuery========== 
CREATE INDEX [IX_User_UserId] ON [Advertisements] ([User_UserId]) 
ExecuteNonQuery========== 
ALTER TABLE [AdImages] ADD CONSTRAINT [FK_AdImages_Advertisements_Advertisement_Id] FOREIGN KEY ([Advertisement_Id]) REFERENCES [Advertisements] ([Id]) 
ExecuteNonQuery========== 
ALTER TABLE [Advertisements] ADD CONSTRAINT [FK_Advertisements_Users_User_UserId] FOREIGN KEY ([User_UserId]) REFERENCES [Users] ([UserId]) 

Quindi, in realtà è tutto come previsto.

E qui il test ho provato

public static void Run(DbConnection connection) 
{ 
    var ad = new Advertisement 
    { 
     AdImages = new List<AdImage> 
     { 
      new AdImage {Image = "MyImage"} 
     }, 

     Message = "MyMessage", 
     Title = "MyTitle", 
     User = new User() 
    }; 

    using (Context context = new Context(connection)) 
    { 
     context.Advertisements.Add(ad); 
     context.SaveChanges(); 
    } 
} 

che ha prodotto questa query su database di

ExecuteDbDataReader========== 
insert into [Users]([AccessToken]) 
values (null); 
select [UserId] 
from [Users] 
where [UserId] = @@identity 
ExecuteDbDataReader========== 
insert into [Advertisements]([Title], [Message], [User_UserId]) 
values (@p0, @p1, @p2); 
select [Id] 
from [Advertisements] 
where [Id] = @@identity 
@p0 = MyTitle 
@p1 = MyMessage 
@p2 = 1 
ExecuteDbDataReader========== 
insert into [AdImages]([Image], [Advertisement_Id]) 
values (@p0, @p1); 
select [Id] 
from [AdImages] 
where [Id] = @@identity 
@p0 = MyImage 
@p1 = 1 

Il modello è semplicemente perfetto :)

Quindi il problema è da qualche altra parte. Potrebbe essere nel codice, intorno, ad esempio
- da dove provengono gli model e user? Lo stesso contesto che usi in Upsert o in contesti diversi?
- cosa fai in Upsert? Riesci a codificare alcuni riferimenti (da model a un nuovo oggetto)?
- ci sono altre classi mancanti (quelle che ho lasciato vuote) ok?

+0

Grazie per averlo esaminato - sembra che ci sia qualcosa all'interno di 'AbpUser ' che sta causando il problema 'model' è una classe View Model molto semplice che non fa altro che contenere i dati dalla richiesta. 'AbpUser' è una latta di worm piuttosto complicata da https://github.com/aspnetboilerplate – DaveDev

+0

Prima di iniziare a eseguire il debug di boilerplate :) basta verificare che si stia utilizzando un solo contesto (ho letto che l'utente e il modello sono di viewmodel quindi non dovrebbe essere correlato a nessun contesto ma ho qualche dubbio sull'utente). Questa eccezione a volte è correlata a contesti. – bubi

Problemi correlati