I'm trying to do an Upsert of an Advertisement
object which contains a List<AdImage>
. The Advertisement
contains a foreign key that corresponds to a User
. A user
can have zero or more Advertisements
, and an Advertisement
has one or more AdImages
.
The upsert fails with the following:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
Where the inner exeption is:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
The advertisement
is instantiated very simply as:
var ad = new Advertisement
{
AdImages = new List<AdImage>
{
new AdImage {Image = model.Image}
},
Message = model.Message,
Title = model.Title,
User = user,
};
_aAdAppService.UpsertAdvertisement(ad);
The entities in question are defined as:
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; }
}
This is how the relationships are defined:
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);
}
What does the error message mean? I can't see how my relationships are defined incorrectly. How can I resolve this?
This is just a comment but I can't write it in comments...
It's the first time that I see the exception
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
so I tried to reproduce it.
This is the way I implemented the missed classes and the context
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);
}
This are the DDL statements generated by EF during Automatic Migration
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, actually is everything as expected.
And here the test I tried
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();
}
}
That produced this queries on database
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
Your model is simply perfect :)
So the problem is somewhere else.
It could be in the code, around, i.e.
- where do model
and user
come from? Same context that you use in Upsert or different context?
- what do you do in Upsert? Do you cange some references (from model
to a new object)?
- are other missing classes (the ones that I left empty) ok?
The solution to this was to put wrap my endpoint in a UnitOfWork
. I'm not not well-enough versed in the Scriptures of Entity Framework to describe exactly the issue or why this worked, but it did.
This is an example of what worked (though slightly different from the example code above):
[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();
}
The UnitOfWork
attribute is a member of the ASP.NET Boilerplate project, and it's defined as follows:
Summary: This attribute is used to indicate that declaring method is atomic and should be considered as a unit of work. A method that has this attribute is intercepted, a database connection is opened and a transaction is started before call the method. At the end of method call, transaction is commited and all changes applied to the database if there is no exception, othervise it's rolled back.
Remarks: This attribute has no effect if there is already a unit of work before calling this method, if so, it uses the same transaction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With