2016-03-23 3 views
6

Próbuję zrobić Upsert z obiektu Advertisement, który zawiera List<AdImage>. Numer Advertisement zawiera klucz obcy, który odpowiada User. A user może mieć zero lub więcej Advertisements, a Advertisement ma jedno lub więcej AdImages.Jak określić prawidłowe porządkowanie dla operacji zależnych podczas definiowania relacji encji?

upsert nie powiedzie się z następujących czynności: Wystąpił

błąd podczas zapisywania podmioty, które nie narażają klucz obcy właściwości ich relacji. Właściwość EntityEntries zwróci wartość null , ponieważ nie można zidentyfikować pojedynczej encji jako źródła wyjątku. Obsługa wyjątków podczas zapisywania może być łatwiejsza dzięki ujawnieniu właściwości klucza obcego w typach jednostek. Szczegółowe informacje można znaleźć w artykule InnerException pod adresem .

Gdzie wewnętrzna exeption jest:

Nie można określić prawidłową kolejność dla operacji zależnych. Zależności mogą występować z powodu ograniczeń klucza obcego, wymagań modelu lub wartości generowanych przez sklep.

advertisement jest tworzony w bardzo prosty sposób, jak:

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

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

_aAdAppService.UpsertAdvertisement(ad); 

Podmioty o których mowa, są określone jako:

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; } 
} 

ten sposób relacje są określone: ​​

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); 

} 

Co oznacza komunikat o błędzie szałas oznacza? Nie widzę, jak moje relacje są zdefiniowane niepoprawnie. Jak mogę to rozwiązać?

+0

Tylko rzuca okiem na to, a to może być strzał w ciemność. Czy próbowałeś zmienić publiczną listę: AdImages {get; set;} w swoim modelu reklamy do właściwości nawigacji EF: publiczny wirtualny ICollection AdImages {get; ustawić;}? – JDupont

+1

Hmm, nie jestem pewien, czy pomogłabym, ale spróbuj usunąć 'modelBuilder.Entity () .HasRequired (x => x.Advertisement);', relacja jest już poprawnie skonfigurowana przez poprzednią instrukcję, a to może ją niepoprawnie zmienić ponieważ jest niekompletna. –

Odpowiedz

1

Rozwiązaniem tego było umieszczenie wrap mój punkt końcowy w UnitOfWork. Nie jestem wystarczająco dobrze zaznajomiony z Pismem Świętym w Entity Framework, aby opisać dokładnie problem lub dlaczego to zadziałało, ale tak się stało.

To jest przykład tego, co działało (choć nieznacznie różni się od kodu przykładu):

[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(); 
} 

Atrybut UnitOfWork jest członkiem projektu ASP.NET boilerplate, i jest zdefiniowany następująco:

Podsumowanie: Ten atrybut jest używany do wskazania, że ​​deklarowanie metody ma charakter atomowy i powinno być traktowane jako jednostka pracy. Metoda przechwytująca ten atrybut, połączenie z bazą danych jest otwierane, a transakcja jest uruchamiana przed wywołaniem metody. Na końcu wywołania metody transakcja zostaje zatwierdzona, a wszystkie zmiany zastosowane do bazy danych, jeśli nie ma wyjątków, inaczej są wycofywane.

Uwagi: Ten atrybut nie ma zastosowania, jeśli przed wywołaniem tej metody istnieje już jednostka pracy, jeśli tak, korzysta z tej samej transakcji.

1

To tylko komentarz, ale nie mogę pisać w komentarzach ...

To pierwszy raz, kiedy widzę wyjątek

stanie określić prawidłową kolejność dla operacji zależnych. Zależności mogą występować z powodu ograniczeń klucza obcego, wymagań modelu lub wartości generowanych przez sklep.

więc próbowałem go odtworzyć.

To jest sposób, w jaki realizowane nieodebrane klas i kontekst

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); 

} 

To są DDL generowanych przez EF podczas migracji automatycznej

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]) 

So, właściwie to wszystko zgodnie z oczekiwaniami.

A oto test Próbowałem

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(); 
    } 
} 

produkowany na bazie tego zapytania

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 

Twój model jest po prostu idealny :)

Więc problem jest gdzieś indziej. Może być w kodzie, około, tj.
- skąd pochodzą model i user? Taki sam kontekst, którego używasz w Upsert lub innym kontekście?
- co robisz w Upsert? Czy udało ci się odnieść niektóre referencje (od model do nowego obiektu)?
- są inne brakujące klasy (te, które zostawiłem puste) w porządku?

+0

Dziękuję za patrzenie w to - wydaje się, że coś wewnątrz 'AbpUser ' powoduje problem "model" jest bardzo prostą klasą View Model, która nie robi nic poza zawartością danych z żądania. 'AbpUser' to dość skomplikowana puszka robaków z https://github.com/aspnetboilerplate – DaveDev

+0

Zanim zaczniesz debugować boilerplate :) po prostu sprawdź, czy używasz tylko jednego kontekstu (czytałem, że użytkownik i model są z viewmodelu, więc nie powinien być związany z żadnym kontekstem, ale mam dubt na użytkownika). Ten wyjątek jest czasami związany z kontekstami. – bubi