I've read quite a few posts that refer to this exception, but the use cases differ from mine
I have a new entity (Bonus
) that I am trying to save. It has two child collections. If I save it WITHOUT the two child collections (Currencies
and Tiers
), an INSERT
statement is generated and the entity is saved correctly.
However, when I add the two collections, I get the above exception and the SQL that is generated is actually trying to UPDATE
the tables corresponding to those collections when it should be generating an INSERT
.
Here are the queries generate with and without the child collections:
Without child collections:
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Bonus] ([Allocation], [BonusExpiryTerm], [BonusFreeSpinsConvention], [BonusTermsUrl], [BonusValueType], [CasinoId], [DateAdded], [DateEnd], [DateStart], [DateUpdated], [EligibilityDescription], [FreeBetConvention], [Guid], [IsActive], [IsBonusPagePresence], [IsBonusTermsLink], [IsClaimInstructions], [IsExclusive], [IsHighRollerBonus], [IsHomePagePresence], [IsNew], [IsPrimary], [IsPromotionPagePresence], [IsSecondChanceBonus], [IsSpecial], [IsSplitBonusAccountInCashier], [LongDescription], [PrimaryBonusType], [QualityAndEaseOfUse], [SecondaryBonusType], [ShortDescription], [SportsbookBonusTrigger], [SportsbookExcludedBettingTypesAndMarkets], [SportsbookExcludedPaymentMethods], [SportsbookMinimumOddsAllowed], [SportsbookPermittedBettingTypesAndMarkets], [SportsbookPermittedPaymentMethods], [WageringRequirementsDescription], [WageringRequirementsExcludedGamesCsv], [WageringRequirementsIsTrackingReporting], [WageringRequirementsPlayThrough], [WageringRequirementsRequirementsUrl], [WageringRequirementsSignificantTerms], [WageringRequirementsTermsCopy], [WageringRequirementsVipWageringMultiple], [WageringRequirementsVipWageringValue], [WithdrawalRestrictionsAdditionalPenaltyNotes], [WithdrawalRestrictionsAllowLowRiskWagering], [WithdrawalRestrictionsAllowOversizedBets], [WithdrawalRestrictionsBonusAbuseNotes], [WithdrawalRestrictionsDescription], [WithdrawalRestrictionsDisputeResolutionProcess], [WithdrawalRestrictionsDisputeResolutionProcessDescription], [WithdrawalRestrictionsEarlyWithdrawalPenalty], [WithdrawalRestrictionsGamePenalty], [WithdrawalRestrictionsGroupCrossoverPenalty], [WithdrawalRestrictionsGroupCrossoverRestriction], [WithdrawalRestrictionsManagerOverrideClause], [WithdrawalRestrictionsPlayingPatternReview], [WithdrawalRestrictionsReversalOption], [WithdrawalRestrictionsSingleBetLimit], [WithdrawalRestrictionsSlotsDoubleUp], [WithdrawalRestrictionsType], [WithdrawalRestrictionsVideoPokerDoubleUp])
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26, @p27, @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35, @p36, @p37, @p38, @p39, @p40, @p41, @p42, @p43, @p44, @p45, @p46, @p47,@p48, @p49, @p50, @p51, @p52, @p53, @p54, @p55, @p56, @p57, @p58, @p59, @p60, @p61, @p62, @p63);
SELECT [Id]
FROM [Bonus]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 int,@p2 int,@p3 nvarchar(4000),@p4 int,@p5 int,@p6 datetime2(7),@p7 datetime2(7),@p8 datetime2(7),@p9 datetime2(7),@p10 nvarchar(4000),@p11 int,@p12 uniqueidentifier,@p13 bit,@p14 bit,@p15 bit,@p16 bit,@p17 bit,@p18 bit,@p19 bit,@p20 bit,@p21 bit,@p22 bit,@p23 bit,@p24 bit,@p25 bit,@p26 nvarchar(4000),@p27 int,@p28 int,@p29 int,@p30 nvarchar(4000),@p31 int,@p32 nvarchar(4000),@p33 nvarchar(4000),@p34 nvarchar(4000),@p35 nvarchar(4000),@p36 nvarchar(4000),@p37 nvarchar(4000),@p38 nvarchar(4000),@p39 bit,@p40 int,@p41 nvarchar(4000),@p42 nvarchar(4000),@p43 nvarchar(4000),@p44 int,@p45 int,@p46 nvarchar(4000),@p47 int,@p48 int,@p49 nvarchar(4000),@p50 nvarchar(4000),@p51 int,@p52 nvarchar(4000),@p53 int,@p54 int,@p55 int,@p56 int,@p57 int,@p58 int,@p59 int,@p60 int,@p61 int,@p62 int,@p63 int',@p0=0,@p1=NULL,@p2=0,@p3=NULL,@p4=0,@p5=11,@p6='2023-08-15 10:32:27.5650372',@p7=NULL,@p8=NULL,@p9=NULL,@p10=NULL,@p11=NULL,@p12='18EC1B53-FE52-4B6F-BF60-8FB2D0723F79',@p13=0,@p14=0,@p15=0,@p16=0,@p17=0,@p18=0,@p19=0,@p20=0,@p21=0,@p22=0,@p23=0,@p24=0,@p25=0,@p26=NULL,@p27=1,@p28=0,@p29=0,@p30=NULL,@p31=NULL,@p32=NULL,@p33=NULL,@p34=NULL,@p35=NULL,@p36=NULL,@p37=NULL,@p38=NULL,@p39=0,@p40=0,@p41=NULL,@p42=NULL,@p43=NULL,@p44=NULL,@p45=NULL,@p46=NULL,@p47=NULL,@p48=NULL,@p49=NULL,@p50=NULL,@p51=NULL,@p52=NULL,@p53=NULL,@p54=NULL,@p55=NULL,@p56=NULL,@p57=NULL,@p58=NULL,@p59=NULL,@p60=NULL,@p61=NULL,@p62=NULL,@p63=NULL
With child collections:
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Bonus] ([Allocation], [BonusExpiryTerm], [BonusFreeSpinsConvention], [BonusTermsUrl], [BonusValueType], [CasinoId], [DateAdded], [DateEnd], [DateStart], [DateUpdated], [EligibilityDescription], [FreeBetConvention], [Guid], [IsActive], [IsBonusPagePresence], [IsBonusTermsLink], [IsClaimInstructions], [IsExclusive], [IsHighRollerBonus], [IsHomePagePresence], [IsNew], [IsPrimary], [IsPromotionPagePresence], [IsSecondChanceBonus], [IsSpecial], [IsSplitBonusAccountInCashier], [LongDescription], [PrimaryBonusType], [QualityAndEaseOfUse], [SecondaryBonusType], [ShortDescription], [SportsbookBonusTrigger], [SportsbookExcludedBettingTypesAndMarkets], [SportsbookExcludedPaymentMethods], [SportsbookMinimumOddsAllowed], [SportsbookPermittedBettingTypesAndMarkets], [SportsbookPermittedPaymentMethods], [WageringRequirementsDescription], [WageringRequirementsExcludedGamesCsv], [WageringRequirementsIsTrackingReporting], [WageringRequirementsPlayThrough], [WageringRequirementsRequirementsUrl], [WageringRequirementsSignificantTerms], [WageringRequirementsTermsCopy], [WageringRequirementsVipWageringMultiple], [WageringRequirementsVipWageringValue], [WithdrawalRestrictionsAdditionalPenaltyNotes], [WithdrawalRestrictionsAllowLowRiskWagering], [WithdrawalRestrictionsAllowOversizedBets], [WithdrawalRestrictionsBonusAbuseNotes], [WithdrawalRestrictionsDescription], [WithdrawalRestrictionsDisputeResolutionProcess], [WithdrawalRestrictionsDisputeResolutionProcessDescription], [WithdrawalRestrictionsEarlyWithdrawalPenalty], [WithdrawalRestrictionsGamePenalty], [WithdrawalRestrictionsGroupCrossoverPenalty], [WithdrawalRestrictionsGroupCrossoverRestriction], [WithdrawalRestrictionsManagerOverrideClause], [WithdrawalRestrictionsPlayingPatternReview], [WithdrawalRestrictionsReversalOption], [WithdrawalRestrictionsSingleBetLimit], [WithdrawalRestrictionsSlotsDoubleUp], [WithdrawalRestrictionsType], [WithdrawalRestrictionsVideoPokerDoubleUp])
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26, @p27, @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35, @p36, @p37, @p38, @p39, @p40, @p41, @p42, @p43, @p44, @p45, @p46, @p47,@p48, @p49, @p50, @p51, @p52, @p53, @p54, @p55, @p56, @p57, @p58, @p59, @p60, @p61, @p62, @p63);
SELECT [Id]
FROM [Bonus]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 int,@p1 int,@p2 int,@p3 nvarchar(4000),@p4 int,@p5 int,@p6 datetime2(7),@p7 datetime2(7),@p8 datetime2(7),@p9 datetime2(7),@p10 nvarchar(4000),@p11 int,@p12 uniqueidentifier,@p13 bit,@p14 bit,@p15 bit,@p16 bit,@p17 bit,@p18 bit,@p19 bit,@p20 bit,@p21 bit,@p22 bit,@p23 bit,@p24 bit,@p25 bit,@p26 nvarchar(4000),@p27 int,@p28 int,@p29 int,@p30 nvarchar(4000),@p31 int,@p32 nvarchar(4000),@p33 nvarchar(4000),@p34 nvarchar(4000),@p35 nvarchar(4000),@p36 nvarchar(4000),@p37 nvarchar(4000),@p38 nvarchar(4000),@p39 bit,@p40 int,@p41 nvarchar(4000),@p42 nvarchar(4000),@p43 nvarchar(4000),@p44 int,@p45 int,@p46 nvarchar(4000),@p47 int,@p48 int,@p49 nvarchar(4000),@p50 nvarchar(4000),@p51 int,@p52 nvarchar(4000),@p53 int,@p54 int,@p55 int,@p56 int,@p57 int,@p58 int,@p59 int,@p60 int,@p61 int,@p62 int,@p63 int',@p0=0,@p1=NULL,@p2=0,@p3=NULL,@p4=0,@p5=11,@p6='2023-08-15 10:32:27.5650372',@p7=NULL,@p8=NULL,@p9=NULL,@p10=NULL,@p11=NULL,@p12='18EC1B53-FE52-4B6F-BF60-8FB2D0723F79',@p13=0,@p14=0,@p15=0,@p16=0,@p17=0,@p18=0,@p19=0,@p20=0,@p21=0,@p22=0,@p23=0,@p24=0,@p25=0,@p26=NULL,@p27=1,@p28=0,@p29=0,@p30=NULL,@p31=NULL,@p32=NULL,@p33=NULL,@p34=NULL,@p35=NULL,@p36=NULL,@p37=NULL,@p38=NULL,@p39=0,@p40=0,@p41=NULL,@p42=NULL,@p43=NULL,@p44=NULL,@p45=NULL,@p46=NULL,@p47=NULL,@p48=NULL,@p49=NULL,@p50=NULL,@p51=NULL,@p52=NULL,@p53=NULL,@p54=NULL,@p55=NULL,@p56=NULL,@p57=NULL,@p58=NULL,@p59=NULL,@p60=NULL,@p61=NULL,@p62=NULL,@p63=NULL
go
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [BonusCasinoCurrency] SET [BonusId] = @p64
OUTPUT 1
WHERE [Id] = @p65;
UPDATE [BonusMatchTier] SET [BonusId] = @p66
OUTPUT 1
WHERE [Id] = @p67;
',N'@p65 int,@p64 int,@p67 int,@p66 int',@p65=0,@p64=9435,@p67=0,@p66=9435
go
In this case, @p66=9435
is the correct Id for the next Bonus. I assume the "actually affected 0 row(s);" refers to the UPDATE
because it updates no rows
The second query block appears to be rolled back and does not insert the bonus.
Here is the EF code:
The BonusService
and CasinoService
are where the EF context is accessible.
[Test]
public void Create()
{
BonusMatch bonusMatch = new BonusMatch();
using (BonusService bonusService = new BonusService())
using (CasinoService casinoService = new CasinoService())
{
Casino casino = casinoService.Find(11, true);
bonusMatch.Casino = casino;
bonusMatch.Tiers.Add(new BonusMatchTier(0, 1, 1, 1, 1, "test", "test"));
bonusMatch.BonusCasinoCurrencies.Add(new BonusCasinoCurrency(casino.AvailableCurrencies.First(), true));
bonusService.Add(bonusMatch);
}
// Assert.Pass();
}
You'll notice the AutoMapper Map
code, This works fine. This is inside BonusService
public Business.Bonus Add(Business.Bonus dtoBonus)
{
Domain.Bonus domainBonus = _context.Map<Domain.Bonus>(dtoBonus);
_context.Add(domainBonus);
_context.SaveChanges();
dtoBonus.SetId(domainBonus.Id);
return dtoBonus;
}
EF Model config:
public class CasinoConfiguration : IEntityTypeConfiguration<Casino>
{
public void Configure(EntityTypeBuilder<Casino> modelBuilder)
{
modelBuilder.ToTable(tb => tb.HasTrigger("TR_upd_Casino"));
modelBuilder.HasMany(c => c.Bonuses)
.WithOne(b => b.Casino)
.OnDelete(DeleteBehavior.Cascade);
}
}
public class BonusConfiguration : IEntityTypeConfiguration<Bonus>
{
public void Configure(EntityTypeBuilder<Bonus> modelBuilder)
{
#region Bonus Mapping
modelBuilder.HasDiscriminator(d => d.SecondaryBonusType)
.HasValue(typeof(BonusMatch), BonusTypes.Match)
.HasValue(typeof(BonusNoDeposit), BonusTypes.NoDeposit)
.HasValue(typeof(BonusFreeplay), BonusTypes.Freeplay)
.HasValue(typeof(BonusPostWager), BonusTypes.PostWager)
.HasValue(typeof(BonusCashback), BonusTypes.Cashback)
;
modelBuilder
.HasOne(b => b.Casino)
.WithMany(c => c.Bonuses)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.HasMany(c => c.BonusCasinoCurrencies)
.WithOne(c => c.Bonus)
.OnDelete(DeleteBehavior.Cascade);
}
}
In the main model config class
modelBuilder.Entity<BonusMatch>()
.OwnsMany(b => b.Tiers,
t =>
{
t.ToTable("BonusMatchTier")
.WithOwner(b=> b.Bonus)
.HasForeignKey("BonusId");
}
)
.Ignore(b => b.HasTiers);
My understanding is that EF would know it must INSERT
those child collections because they belong to a new entity that, itself, is correctly being INSERTED
.
So why does EF think that these two collections exist and should be UPDATED
instead of being implicitly new because they exist on a new entity?
UPDATE As requested, here are the class definitions
public abstract class Bonus : EntityBase
{
private IList<BonusCasinoCurrency> _bonusCasinoCurrencies;
public virtual IList<BonusCasinoCurrency> BonusCasinoCurrencies
{
get { return _bonusCasinoCurrencies; }
set { _bonusCasinoCurrencies = value; }
}
public Bonus() : base()
{
PrimaryBonusType = PrimaryBonusType.SignUp;
}
}
public class BonusMatch : Bonus
{
private List<BonusMatchTier> _tiers;
public virtual List<BonusMatchTier> Tiers
{
get { return _tiers; }
protected set { _tiers = value; }
}
public BonusMatch() : base ()
{
_tiers = new List<BonusMatchTier>();
}
}
public class BonusMatchTier
{
public virtual string BonusCode { get; protected set; }
public virtual decimal MinDepositAmount { get; protected set; }
public int? FreeSpinsWagering { get; protected set; }
protected BonusMatchTier() { }
}