I'm using entity framework in ASP.NET Core Web API project
I have built the model using the scaffold command from the package manager console and everything is working properly, I can edit the tables, I can insert data, I can update, get lists of data using many different criteria.
any ways my problem is when I try to get all the data from a table named 'Branches'
I get an error of type Microsoft.Data.SqlClient.SqlException with a message says "Invalid column name 'UserIndx'."
this is my code that tries to get the data
/// <summary>
/// Function that returns a list with all the branch table data.
/// </summary>
public List<Branch> getBranches()
{
List<Branch> list = new List<Branch>();
try
{
list = db.Branches.ToList();
return list;
}
catch(Exception ex)
{
er.writeToErrorLog("BLDataEntry", "Branches", "getBranches", ex);
return list;
}
}
This is my branch class that was auto generated by the scaffold command
public partial class Branch
{
public int Indx { get; set; }
public string Branch1 { get; set; } = null!;
public string? IataNo { get; set; }
public int? VatDealer { get; set; }
public string? Tel { get; set; }
public string? Tel2 { get; set; }
public string? Fax { get; set; }
public string? Fax2 { get; set; }
public string? Address { get; set; }
public int? SagentId { get; set; }
public int? OutAccGroup { get; set; }
public int? IntAccGroup { get; set; }
public int? IncAccGroup { get; set; }
public int? IspAccGroup { get; set; }
public byte[]? RptLogo { get; set; }
public byte[]? AppLogo { get; set; }
public int? Company { get; set; }
public DateTime OpenDate { get; set; }
public bool SellOnly { get; set; }
public bool? IsOblClientDetails { get; set; }
public DateTime? CloseDate { get; set; }
public string? LocalName { get; set; }
public string? LocalAddress { get; set; }
public string? Longitude { get; set; }
public string? Latitude { get; set; }
public string? WorkingDaysHours { get; set; }
public string? Iban { get; set; }
public string? KvKnr { get; set; }
public string? Btwnr { get; set; }
public string? Biccode { get; set; }
public byte[]? AddLogos { get; set; }
public string? Email { get; set; }
public string? WebSite { get; set; }
public virtual ICollection<AccBank> AccBanks { get; } = new List<AccBank>();
public virtual ICollection<Agent> Agents { get; } = new List<Agent>();
public virtual ICollection<Department> Departments { get; } = new List<Department>();
public virtual ICollection<SpecialProductDistribution> SpecialProductDistributions { get; } = new List<SpecialProductDistribution>();
}
This is the code from the dbContext.cs that also was auto generated by the scaffold command by entity framework
modelBuilder.Entity<Branch>(entity =>
{
entity.HasKey(e => e.Indx);
entity.Property(e => e.AddLogos).HasColumnType("image");
entity.Property(e => e.Address).HasMaxLength(50);
entity.Property(e => e.AppLogo).HasColumnType("image");
entity.Property(e => e.Biccode)
.HasMaxLength(10)
.HasColumnName("BICcode");
entity.Property(e => e.Branch1)
.HasMaxLength(50)
.HasColumnName("Branch");
entity.Property(e => e.Btwnr)
.HasMaxLength(15)
.HasColumnName("BTWnr");
entity.Property(e => e.CloseDate).HasColumnType("datetime");
entity.Property(e => e.Email).HasMaxLength(100);
entity.Property(e => e.Fax).HasMaxLength(15);
entity.Property(e => e.Fax2).HasMaxLength(15);
entity.Property(e => e.IataNo).HasMaxLength(12);
entity.Property(e => e.Iban)
.HasMaxLength(50)
.HasColumnName("IBAN");
entity.Property(e => e.IsOblClientDetails)
.IsRequired()
.HasDefaultValueSql("((1))");
entity.Property(e => e.KvKnr)
.HasMaxLength(15)
.HasColumnName("KvKNr");
entity.Property(e => e.Latitude)
.HasMaxLength(15)
.HasColumnName("latitude");
entity.Property(e => e.LocalAddress).HasMaxLength(100);
entity.Property(e => e.LocalName).HasMaxLength(100);
entity.Property(e => e.Longitude)
.HasMaxLength(15)
.HasColumnName("longitude");
entity.Property(e => e.OpenDate)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime");
entity.Property(e => e.RptLogo).HasColumnType("image");
entity.Property(e => e.SagentId).HasColumnName("SAgentID");
entity.Property(e => e.Tel).HasMaxLength(15);
entity.Property(e => e.Tel2).HasMaxLength(15);
entity.Property(e => e.WebSite).HasMaxLength(100);
entity.Property(e => e.WorkingDaysHours).HasMaxLength(255);
});
I have searched the whole project for a column with the name UserIndx and it doesn't exist in my whole solution I have tried to pull a specific column from that table and it worked right as it should
the problem is basically when I wanna grab all the table into a list.
I have tried getting back data when I asked for specific columns.
I have an Item entity:
public class Item
{
public long Id { get; set; }
public string Name { get; set; }
public string Skuid { get; set; }
public double Price { get; set; }
public double Amount { get; set; }
}
And an Order entity:
public class Order
{
public long Id { get; set; }
public DateTime Date { get; set; }
public StatusEnum Status { get; set; }
public long SellerId { get; set; }
public Seller Seller { get; set; }
public double Total { get; set; }
public IList<Item> Items { get; set; }
}
My objective is to save the Order containing the items in the Database, but when the request is made, only the reference of my entity "Seller" is saved, but not the references of the items.
I did the following mapping on the "Order" entity:
public class OrderMap : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("Orders");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.ValueGeneratedOnAdd()
.UseIdentityColumn();
builder.Property(x => x.Date)
.IsRequired()
.HasColumnName("DateSale")
.HasColumnType("DATETIME")
.HasDefaultValueSql("GETDATE()");
builder.Property(x => x.Total)
.IsRequired()
.HasColumnName("Total")
.HasColumnType("DECIMAL");
builder.Property(x => x.Status)
.IsRequired()
.HasConversion(
v => v.ToString(),
v => (StatusEnum)Enum.Parse(typeof(StatusEnum), v))
.HasColumnName("Status")
.HasColumnType("NVARCHAR") //verificar se nao vai dar conflito
.HasMaxLength(120);
builder
.HasOne(x => x.Seller)
.WithOne()
.HasConstraintName("FK_Seller_Order")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasMany(x => x.Items)
.WithOne()
.HasConstraintName("FK_Item_Order")
.IsRequired()
.OnDelete(DeleteBehavior.Cascade); //
}
}
What is the correct mapping to do?
Please first add these properties OrderId and Order to your Item entity class. This way you are instructions the framework to create an OrderId foreign key
column in the database.
public class Item
{
public long Id { get; set; }
public string Name { get; set; }
public string Skuid { get; set; }
public double Price { get; set; }
public double Amount { get; set; }
public long OrderId { get; set; }
public Order Order {get; set; }
}
And perhaps this part
builder.HasMany(x => x.Items).WithOne()
should be like this
builder.HasMany(x => x.Items).WithOne(x => x.Order)
I have two tables and related code files, Request and RequestLine. One Request can have many RequestLines. I used a database first approach and the models were automatically created using scaffolding. I am using Entity Framework and DTOs etc
I have run into an issue where i'm getting a Reference Loop error in my API when running the POST method.
The issue occurs because the scaffold put a Request property in the RequestLine object
public virtual Request Request { get; set; } = null!;
With my GET method, in the controller I was able to point it to its related DTO, where I removed the Request field which would have caused the reference loop but i'm not sure how I would achieve the same with the POST method. I created a RequestCreateDTO and RequestLineCreateDTO without the reference field loop but I can't figure out how to use this in my POST.
I have found two ways of avoiding this issue
The first is to remove the Request property from RequestLine model, then remove the relationship from RequesLine context
Alternatively, I can add this to my Program.cs to ignore the Reference Loop
builder.Services.AddControllers().AddJsonOptions(x =>
x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
Although both work, neither seems like to correct way to handle this. So I was wondering how I go about handling it properly? I want to POST a Request, which contains a RequestLine - But I want them to use my RequestCreateDTO and RequestLineCreateDTO.
The related code is below.
The data context for Request and RequestLines is:
modelBuilder.Entity<Request>(entity =>
{
entity.ToTable("Request", "rmr");
entity.Property(e => e.CreatedOn).HasColumnType("datetime");
entity.Property(e => e.RaisedBy).HasMaxLength(256);
entity.Property(e => e.WorklistName).HasMaxLength(32);
//entity.HasOne(d => d.WorklistNameNavigation)
// .WithMany(p => p.Requests)
// .HasForeignKey(d => d.WorklistName)
// .HasConstraintName("FK_RMR_REQUEST_WORKLIST");
});
modelBuilder.Entity<RequestLine>(entity =>
{
entity.HasKey(e => new { e.RequestId, e.LineNumber })
.HasName("PK_RMR_REQUESTLINE");
entity.ToTable("RequestLine", "rmr");
entity.Property(e => e.Batch).HasMaxLength(32);
entity.Property(e => e.CancelBy).HasMaxLength(256);
entity.Property(e => e.CancelDate).HasColumnType("datetime");
entity.Property(e => e.CostCentre).HasMaxLength(32);
entity.Property(e => e.Destination).HasMaxLength(32);
entity.Property(e => e.FirstDeliveryDate).HasColumnType("datetime");
entity.Property(e => e.ProcessOrder).HasMaxLength(32);
entity.Property(e => e.Sku)
.HasMaxLength(32)
.HasColumnName("SKU");
entity.Property(e => e.SubmittedDate).HasColumnType("datetime");
//entity.HasOne(d => d.DestinationNavigation)
// .WithMany(p => p.RequestLines)
// .HasForeignKey(d => d.Destination)
// .OnDelete(DeleteBehavior.ClientSetNull)
// .HasConstraintName("FK_RMR_REQUESTLINE_DESTINATION");
entity.HasOne(d => d.Request)
.WithMany(p => p.RequestLines)
.HasForeignKey(d => d.RequestId)
.HasConstraintName("FK_RMR_REQUESTLINE_REQUEST");
});
The related data models are as follows:
Request
public partial class Request
{
public Request()
{
RequestLines = new HashSet<RequestLine>();
}
public long Id { get; set; }
public string RaisedBy { get; set; } = null!;
public DateTime CreatedOn { get; set; }
public string? WorklistName { get; set; }
//public virtual Worklist? WorklistNameNavigation { get; set; }
public virtual ICollection<RequestLine> RequestLines { get; set; }
}
RequestLine
public partial class RequestLine
{
public long RequestId { get; set; }
public int LineNumber { get; set; }
public string Sku { get; set; } = null!;
public string Batch { get; set; } = null!;
public int Quantity { get; set; }
public string? CostCentre { get; set; }
public string? ProcessOrder { get; set; }
public string Destination { get; set; } = null!;
public DateTime FirstDeliveryDate { get; set; }
public DateTime? SubmittedDate { get; set; }
public DateTime? CancelDate { get; set; }
public string? CancelBy { get; set; }
}
}
and the DTOs for the POST method is:
Request
public class RequestCreateDTO
{
public RequestCreateDTO()
{
RequestLines = new HashSet<RequestLineCreateDTO>();
}
[Required]
[StringLength(256)]
public string RaisedBy { get; set; } = null!;
[Required]
public DateTime CreatedOn { get; set; }
[Required]
[StringLength(32)]
public string? WorklistName { get; set; }
//public virtual Worklist? WorklistNameNavigation { get; set; }
public virtual ICollection<RequestLineCreateDTO> RequestLines { get; set; }
}
RequestLine
public class RequestLineCreateDTO
{
[Required]
public int LineNumber { get; set; }
[Required]
[StringLength(32)]
public string Sku { get; set; } = null!;
[Required]
[StringLength(32)]
public string Batch { get; set; } = null!;
public int Quantity { get; set; }
[StringLength(32)]
public string? CostCentre { get; set; }
[StringLength(32)]
public string? ProcessOrder { get; set; }
[Required]
[StringLength(32)]
public string Destination { get; set; } = null!;
public DateTime FirstDeliveryDate { get; set; }
public DateTime? SubmittedDate { get; set; }
public DateTime? CancelDate { get; set; }
[Required]
[StringLength(256)]
public string? CancelBy { get; set; }
}
The point at which i'm having an issue is my POST method and the controller looks like this
[HttpPost]
public async Task<ActionResult<RequestCreateDTO>> PostRequest(RequestCreateDTO requestDto)
{
var request = mapper.Map<Request>(requestDto);
//var requestLine = mapper.Map<RequestLine>(requestLineDto);
if (_context.Requests == null)
{
return Problem("Entity set 'RawcliffeDatastoreContext.Requests' is null.");
}
await _context.Requests.AddAsync(request);
//await _context.RequestLines.AddAsync(requestLine);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetRequest), new { id = request.Id }, request);
}
I want to get, update, delete and insert data in a view. So far I can get and update data from the view. I use Entity Framework Core 2.1 version.
This is the view model:
public partial class vwVacation: IEntity
{
public int EmployeeId { get; private set; }
public string EmployeeName { get; private set; }
public string EmployeeSurName { get; private set; }
public string EmployeeOccupation { get; private set; }
public string EmployeePhone { get; private set; }
public string EmployeeEmail { get; private set; }
public int HeadQuartersId { get; private set; }
public string HeadQuartersName { get; private set; }
public string HeadQuartersCode { get; private set; }
public int VacationId { get; set; }
public int VacationTypeId { get; set; }
public string VacationType{ get; private set; }
[Column("HistoryId")]
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int VacationDaysTake{ get; set; }
public bool IsApproved { get; set; }
}
This is the code that I write in the entity context:
public virtual DbSet<vwVacation> vwVacation{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<vwVacation>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.EndDate).HasColumnType("datetime");
entity.Property(e => e.StartDate).HasColumnType("datetime");
entity.Property(e => e.IsApproved)
.HasColumnType("bit")
.HasDefaultValueSql("('0')");
entity.ToTable("vwVacation");
entity.Property(e => e.EmployeeId)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.EmployeeName)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.EmployeeSurName)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.EmployeeOccupation)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.EmployeePhone)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.EmployeeEmail)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.HeadQuartersId)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.HeadQuartersName)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.HeadQuartersCode)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
entity.Property(e => e.VacationType)
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw;
});
}
What I need to do to insert data in my view and entity framework know which column are for insert and which column are ReadOnly ?
I have scoffeled all my models for the project from an existing database, using EntityFramework's Scaffold-DbContext command.
EntityFramework did a perfect job in creating all of the models mapped to a table and generating the Fluent API fonfiguration code.
From what I have noticed, the Fluent API code is missing the configuration of the Primary Key field, although it has added the propery to the generated model.
Here is an example of one of my generated classes and its corresponding Fluent API code:
public partial class Account
{
public int AccountId { get; set; } //Primary Key
public int CompanyId { get; set; }
public int CompanyAccountTypeId { get; set; }
public int? CompanyAccountGroupId { get; set; }
public int? RegionId { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public string Address { get; set; }
public string Email { get; set; }
public string IncludeEscalationEmail { get; set; }
public decimal? Gpslat { get; set; }
public decimal? Gpslong { get; set; }
public string Telephone { get; set; }
public string Vatnumber { get; set; }
public bool AutoReceive { get; set; }
public bool AutoIssue { get; set; }
public bool? IsBillableToAccount { get; set; }
public DateTime? BillingStart { get; set; }
public bool? IsEquipmentDepot { get; set; }
public bool? IsShiftAttendanceEnabled { get; set; }
public int? ShiftMinHoursForLunchDeduction { get; set; }
public TimeSpan? NightShiftStart { get; set; }
public TimeSpan? NightShiftEnd { get; set; }
public int? ShiftStartDayOfMonth { get; set; }
public TimeSpan? OperatingHoursStart { get; set; }
public TimeSpan? OperatingHoursEnd { get; set; }
public int? LoadBays { get; set; }
public int? LoadInterval { get; set; }
public int? ArrivalInterval { get; set; }
public TimeSpan? OverrideStockTakeCloseBalanceTime { get; set; }
public bool? TempIgnoreVendorIssueViaSap { get; set; }
public bool Archived { get; set; }
public DateTime CreatedDate { get; set; }
public int CreatedByPersonId { get; set; }
public DateTime UpdatedDate { get; set; }
public int UpdatedByPersonId { get; set; }
public virtual Company Company { get; set; }
public virtual CompanyAccountGroup CompanyAccountGroup { get; set; }
public virtual CompanyAccountType CompanyAccountType { get; set; }
public virtual Region Region { get; set; }
public virtual ICollection<AccountBalance> AccountBalance { get; set; }
public virtual ICollection<AccountContact> AccountContact { get; set; }
public virtual ICollection<AccountEquipment> AccountEquipment { get; set; }
public virtual ICollection<AccountHrlookup> AccountHrlookup { get; set; }
public virtual ICollection<AccountPickVolumeDefault> AccountPickVolumeDefaultAccount { get; set; }
public virtual ICollection<AccountPickVolumeDefault> AccountPickVolumeDefaultPartnerAccount { get; set; }
public virtual ICollection<AccountPickVolumeDetail> AccountPickVolumeDetail { get; set; }
public virtual ICollection<AccountPickVolumePartner> AccountPickVolumePartner { get; set; }
public virtual ICollection<AppUserAccount> AppUserAccount { get; set; }
public virtual ICollection<BiometricTerminal> BiometricTerminal { get; set; }
public virtual ICollection<CompanyVendorAccount> CompanyVendorAccount { get; set; }
public virtual ICollection<EquipmentCheck> EquipmentCheckAccount { get; set; }
public virtual ICollection<EquipmentCheck> EquipmentCheckAccountMaintenance { get; set; }
public virtual ICollection<GantryTransfer> GantryTransferCreditAccount { get; set; }
public virtual ICollection<GantryTransfer> GantryTransferDebitAccount { get; set; }
public virtual ICollection<Order> OrderDepotAccount { get; set; }
public virtual ICollection<Order> OrderPrimaryAccount { get; set; }
public virtual ICollection<Rfequipment> Rfequipment { get; set; }
public virtual ICollection<RfmoveTransaction> RfmoveTransactionPartnerAccount { get; set; }
public virtual ICollection<RfmoveTransaction> RfmoveTransactionPrimaryAccount { get; set; }
public virtual ICollection<ShiftCalendar> ShiftCalendar { get; set; }
public virtual ICollection<ShiftSchedule> ShiftSchedule { get; set; }
public virtual ICollection<ShiftTeam> ShiftTeam { get; set; }
public virtual ICollection<TransferDeviance> TransferDevianceIssueAccount { get; set; }
public virtual ICollection<TransferDeviance> TransferDevianceIssuePartnerAccount { get; set; }
public virtual ICollection<TransferDeviance> TransferDevianceReceiptAccount { get; set; }
public virtual ICollection<TransferDeviance> TransferDevianceReceiptPartnerAccount { get; set; }
public virtual ICollection<TransferJournal> TransferJournalCreditAccount { get; set; }
public virtual ICollection<TransferJournal> TransferJournalDebitAccount { get; set; }
public virtual ICollection<Transfer> TransferPartnerAccount { get; set; }
public virtual ICollection<Transfer> TransferPrimaryAccount { get; set; }
public virtual ICollection<VehicleCheckIn> VehicleCheckIn { get; set; }
public virtual ICollection<XAppUserAccountAccess> XAppUserAccountAccess { get; set; }
}
Here is the Fluent API:
builder.HasIndex(e => new { e.AccountId, e.CompanyAccountTypeId, e.Name, e.Code, e.CompanyId })
.HasName("IX_AccountCompanyType");
builder.HasIndex(e => new { e.Archived, e.AccountId, e.UpdatedDate, e.CompanyId, e.CompanyAccountTypeId, e.CompanyAccountGroupId, e.RegionId })
.HasName("IX_AppUserAccountAccess");
builder.HasIndex(e => new { e.AccountId, e.CompanyId, e.CompanyAccountGroupId, e.RegionId, e.Name, e.Gpslong, e.BillingStart, e.Code, e.Address, e.Email, e.IncludeEscalationEmail, e.Gpslat, e.ArrivalInterval, e.Telephone, e.Vatnumber, e.AutoReceive, e.AutoIssue, e.IsBillableToAccount, e.UpdatedDate, e.IsEquipmentDepot, e.OperatingHoursStart, e.OperatingHoursEnd, e.LoadBays, e.LoadInterval, e.UpdatedByPersonId, e.OverrideStockTakeCloseBalanceTime, e.TempIgnoreVendorIssueViaSap, e.Archived, e.CreatedDate, e.CreatedByPersonId, e.CompanyAccountTypeId })
.HasName("IX_vAccount");
builder.Property(e => e.Address)
.HasMaxLength(500)
.IsUnicode(false);
builder.Property(e => e.BillingStart).HasColumnType("date");
builder.Property(e => e.Code)
.HasMaxLength(50)
.IsUnicode(false);
builder.Property(e => e.CreatedByPersonId).HasColumnName("CreatedBy_PersonId");
builder.Property(e => e.CreatedDate).HasColumnType("datetime");
builder.Property(e => e.Email)
.HasMaxLength(200)
.IsUnicode(false);
builder.Property(e => e.Gpslat)
.HasColumnName("GPSLat")
.HasColumnType("decimal(18, 6)");
builder.Property(e => e.Gpslong)
.HasColumnName("GPSLong")
.HasColumnType("decimal(18, 6)");
builder.Property(e => e.IncludeEscalationEmail)
.HasMaxLength(500)
.IsUnicode(false);
builder.Property(e => e.Name)
.IsRequired()
.HasMaxLength(255)
.IsUnicode(false);
builder.Property(e => e.Telephone)
.HasMaxLength(20)
.IsUnicode(false);
builder.Property(e => e.TempIgnoreVendorIssueViaSap).HasColumnName("temp_IgnoreVendorIssueViaSAP");
builder.Property(e => e.UpdatedByPersonId).HasColumnName("UpdatedBy_PersonId");
builder.Property(e => e.UpdatedDate).HasColumnType("datetime");
builder.Property(e => e.Vatnumber)
.HasColumnName("VATNumber")
.HasMaxLength(50)
.IsUnicode(false);
builder.HasOne(d => d.CompanyAccountGroup)
.WithMany(p => p.Account)
.HasForeignKey(d => d.CompanyAccountGroupId)
.HasConstraintName("FK_Account_CompanyAccountGroup");
builder.HasOne(d => d.CompanyAccountType)
.WithMany(p => p.Account)
.HasForeignKey(d => d.CompanyAccountTypeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Account_CompanyAccountType");
builder.HasOne(d => d.Company)
.WithMany(p => p.Account)
.HasForeignKey(d => d.CompanyId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Account_Company");
builder.HasOne(d => d.Region)
.WithMany(p => p.Account)
.HasForeignKey(d => d.RegionId)
.HasConstraintName("FK_Account_Region");
As you can see, my model has the Primary Key field named AccountId. Why does the mapping for this not get added in the Fluent API code?
It's not being added, because it's a convention that this property is the primary key.
From Conventions in Entity Framework Core:
If a property is named ID or <entity name>ID (not case-sensitive), it will be configured as the primary key. Entity Framework Core will prefer ID over <entity name>ID in the event that a class contains both.