1

I have this a class Order, it has relation with other 2 classes but the issue is with the Id here

public class Order 
{
    public int Id { get; set; }
    public bool IsDeleted { get; set; }
    public string CreatedBy { get; set; }
    public string UpdatedBy { get; set; }
    public DateTime? CreatedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public string Symbol { get; set; }
    //public string SymbolName { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
    public int StatusId { get; set; }
    public OrderStatus Status { get; set; }
    public int TradingActionId { get; set; }
    public TradingAction TradingAction { get; set; }
    public string Notes { get; set; }

}

which I map on this way:

public class OrderMap : IEntityTypeConfiguration<Order>
{
    public override void Configure(EntityTypeBuilder<Order> builder)
    {
        builder.ToTable("Order");

        builder.HasKey(p => p.Id);
        builder.Property(dt => dt.Id).UseIdentityColumn();

        builder.Property(x => x.CreatedBy).HasMaxLength(50).IsRequired();
        builder.Property(x => x.CreatedOn).IsRequired();
        builder.Property(x => x.UpdatedBy).HasMaxLength(50).IsRequired();
        builder.Property(x => x.UpdatedOn).IsRequired();

        builder.HasQueryFilter(app => !app.IsDeleted);
        builder.Property(je => je.Symbol).HasMaxLength(10).IsRequired();
        //builder.Property(je => je.SymbolName).HasMaxLength(300).IsRequired();
        builder.Property(je => je.Quantity).IsRequired();
        builder.Property(je => je.Price).IsRequired();
        builder.Property(je => je.Notes).HasMaxLength(300);

        builder.HasOne<OrderStatus>(order => order.Status)
            .WithMany(os => os.Orders)
            .HasForeignKey(order => order.Id)
            .OnDelete(DeleteBehavior.Restrict)
            .IsRequired(false); ;

        builder.HasOne<TradingAction>(order => order.TradingAction)
            .WithMany(ta => ta.Orders)
            .HasForeignKey(orders => orders.Id)
            .OnDelete(DeleteBehavior.Restrict)
            .IsRequired(false); ;
    }
}

on my repository, I am doing this approach to save, as you can see the properties TradingAction and Status are not filled by me but their Ids are passed to the order to be created.

  var entity = new Order
  {
   TradingActionId = 1,
   StatusId = 2,
   Notes = source.Notes,
   Price = source.Price,
   Symbol = source.Symbol,
   Quantity = source.Quantity,
   CreatedOn = dateTimeNow,
   UpdatedOn = dateTimeNow,
   UpdatedBy = "test",
   CreatedBy = "test"
};
_context.Set<TEntity>().AddAsync(entity);

In this context _context.Set<TEntity>().AddAsync(entity); throws an exception saying:

Cannot insert explicit value for identity column when IDENTITY_INSERT is set to OFF.

Running a SqlProfiler I get this query:

exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Order] ([Id], [CreatedBy], [CreatedOn], [IsDeleted], [Notes], [Price], [Quantity], [StatusId], [Symbol], [TradingActionId], [UpdatedBy], [UpdatedOn])
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11);
',N'@p0 int,@p1 nvarchar(50),@p2 datetime2(7),@p3 bit,@p4 nvarchar(300),@p5 decimal(3,1),@p6 int,@p7 int,@p8 nvarchar(10),@p9 int,@p10 nvarchar(50),@p11 datetime2(7)',@p0=-2147482647,@p1=N'test',@p2='2021-02-26 09:10:21.8811175',@p3=0,@p4=N'test',@p5=20.2,@p6=1000,@p7=1,@p8=N'AAL',@p9=2,@p10=N'test',@p11='2021-02-26 09:10:21.8811175'

which tells me that @p0=-2147482647 when it should be identity autogenerated.

If I change on my mappings this line to this it works and sets the property to identity:

builder.Property(dt => dt.Id).ValueGeneratedOnAdd();

which is the issue here that doesn't allow me to use .UseIdentityColumn() method.

Zinov
  • 3,817
  • 5
  • 36
  • 70
  • What is the question? – Serge Feb 26 '21 at 17:18
  • how can I make it work with UsedIdentityColumn – Zinov Feb 26 '21 at 17:27
  • Good question! What is wrong with ValueGeneratedOnAdd() since it works for you? UsedIdentityColumn was used in previous versions of EF. – Serge Feb 26 '21 at 17:28
  • I was expecting to load extra properties that are part of that object instead of going to the database twice and I can do it with var memberEntry = _context.Entry(newEntityToRet).Member(navProp); if (memberEntry is DbCollectionEntry collectionMember) collectionMember.Load(); The whole thing drove me here to this point, thinking that maybe with the UseIdColumn I wouldn't have any issues with this Member method, but at the end is again a big question why it doesn't work – Zinov Feb 26 '21 at 17:32
  • I still don't see any difference but try to remove this builder.Property(dt => dt.Id).UseIdentityColumn(); at all. it is by default. – Serge Feb 26 '21 at 17:34
  • Since you are configuring the `Order` entity with `IEntityTypeConfiguration`, I'm a bit confused about why you have a `base.Configure(builder);` call. What's the `base` class here and what's inside its `Configure()` method. – atiyar Feb 26 '21 at 23:25
  • @atiyar I just corrected the example – Zinov Feb 27 '21 at 06:29
  • @Zinov Since the generated SQL is sending value for `Id` column, your table has clearly not been configured with an identity column. I'd suggest to delete all existing migrations, create new migration and update/generate the database again. Have you tried something like that? – atiyar Feb 27 '21 at 06:38
  • @atiyar It's the exact opposite. The exception clearly indicates that the table column **is** identity. So the problem is in model, something in the model configuration is preventing the EF Core infrastructure to treat that property as "store generated". Or could just be a bug. Zinov, what exact EF Core version are you using? – Ivan Stoev Feb 27 '21 at 09:10
  • @IvanStoev Oops, totally forgot about the error message. But I actually tried to mean it from EF's perspective, but failed to communicate properly. I mean, it wouldn't try to send an Id value if it considered the table to have identity column. – atiyar Feb 27 '21 at 10:14

1 Answers1

3

First off, the PK fluent configuration

builder.HasKey(p => p.Id);
builder.Property(dt => dt.Id).UseIdentityColumn();

is redundant, since both are true by convention. So the problem should be somewhere else.

And indeed it is in the improper many-to-one FK mapping here (also both redundant)

builder.HasOne<OrderStatus>(order => order.Status)
    .WithMany(os => os.Orders)
    .HasForeignKey(order => order.Id) // <--
    .OnDelete(DeleteBehavior.Restrict)
    .IsRequired(false);

builder.HasOne<TradingAction>(order => order.TradingAction)
    .WithMany(ta => ta.Orders)
    .HasForeignKey(orders => orders.Id) // <--
    .OnDelete(DeleteBehavior.Restrict)
    .IsRequired(false);

Why? Because many-to-one FKs are never auto-generated, they "point" to existing keys in another table, so these misconfiguration calls in fact negate the effect of the previous PK configuration.

To fix the issue (because it also would cause incorrect querying), simply remove them or use respectively

.HasForeignKey(order => order.StatusId)

and

.HasForeignKey(orders => orders.TradingActionId)

Also note that ÌsRequired(false) has no effect on non nullable type FKs like int in yours - they still will be treated as required because the non nullable field cannot have null value. Hence remove them as well, and if you really want optional FKs, change the type of FK properties to the corresponding nullable type (in this case int? (Nullable<int>)).

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343