MVCBeginnersSeptember 2026 · 6 min read

EF Core Relationships and MVC Lookup Dropdowns for Beginners

By go2ismail · Published · .NET 10

TL;DR

A dropdown displays a label but submits an identifier. EF Core maps that identifier as a foreign key; a navigation property represents the related object. The lookup endpoint supplies choices, not proof that every submitted identifier is valid.

The foundation: what the official documentation explains

EF Core represents relationships through foreign keys and navigation properties. A one-to-many relationship can be configured with HasOne, WithMany, and HasForeignKey. An optional relationship permits the foreign key to be absent. Database delete behavior and application soft-delete behavior are separate decisions.

Implementation context: The examples use a .NET 10 MVC application organized into feature folders (Vertical Slice Architecture). Basic C# classes and async/await are assumed. Reference excerpts show selected parts of that application; separately labeled teaching adaptations explain alternatives. They are not complete standalone projects.

Move from Country to Currency

Country is the simple master-data example in the reference application. Currency adds CountryId and a Country navigation property. That is a useful next step because it introduces a relationship without the complexity of a master-detail editor. The relationship reflects this application model; it is not intended as a universal model of every real-world currency arrangement.

The user sees a country name in the form, but saving the name would make the relationship depend on a mutable display value. The form instead submits the identifier. The database can retain the association even when a country display name changes.

Reference excerpt: Data/Entities/Currency.cs

public class Currency : BaseEntity, IHasAutoNumber
{
    public string? AutoNumber { get; set; }
    public string Name { get; set; } = string.Empty;
    public string? Code { get; set; }
    public string Symbol { get; set; } = string.Empty;
    public string? Description { get; set; }
    public string? CountryId { get; set; }
    public Country? Country { get; set; }
}

Read the relationship configuration

The configuration names CountryId as the foreign key and selects NoAction for database delete behavior. WithMany has no inverse collection expression here, so the Country entity does not need to expose a Currency collection for this mapping. A missing inverse navigation is not evidence that no relationship exists.

Reference excerpt: Infrastructures/Databases/Configurations/CurrencyConfiguration.cs

builder.HasOne(e => e.Country)
            .WithMany()
            .HasForeignKey(e => e.CountryId)
            .OnDelete(DeleteBehavior.NoAction);

CountryId is nullable, so the form can represent no selection. The browser turns its blank selection into null before posting. An empty string is not automatically the same thing as a null foreign key at the database boundary; keep this normalization visible in the request flow.

Build a lookup response for the control

The lookup handler selects only Id, Name, and Code and orders the results for display. Returning a small DTO keeps the dropdown independent of unrelated audit fields or future entity navigation properties. The handler lives in the Currency feature because it serves that feature screen, even though it queries Country data.

Reference excerpt: Areas/Admin/Currency/Cqrs/GetCurrencyCountryLookupHandler.cs

var items = await _context.Country
            .OrderBy(x => x.Name ?? x.Code)
            .Select(x => new CountryLookupDto
            {
                Id = x.Id,
                Name = x.Name,
                Code = x.Code
            })
            .ToListAsync(cancellationToken);

Connect options to the form value

The Create script loads /api/currency/country-lookup before initializing its Tom Select control. The control uses id for the value and name for the label. Its onChange callback updates form.countryId. This is browser-side control wiring, not EF relationship configuration; both parts must agree on the identifier representation.

Reference excerpt: Areas/Admin/Currency/Views/Create.cshtml.js

tsCountry = new TomSelect(countryEl, {
                    allowEmptyOption: true,
                    valueField: 'id',
                    labelField: 'name',
                    searchField: 'name',
                    options: countryOptions.value,
                    create: false,
                    onChange: function(value) {
                        form.countryId = value || '';
                    }
                });

The reference lookup loads its available options in one request. That can be pragmatic for a small country list. For a much larger lookup such as customers, consider a searched, bounded endpoint rather than downloading all records. That would be an extension of this pattern, not behavior already implemented by this handler.

Validate selections at the server boundary

The current Currency create validator checks code, name, symbol, and description; it does not explicitly check CountryId existence. The handler assigns CountryId to the entity. In a relational database, an invalid foreign key can therefore surface as a persistence failure rather than a friendly field error. Describe this limitation honestly when teaching from the example.

For an adaptation, check that a supplied identifier refers to a selectable country and return a field-specific error before saving. Retain the relational constraint as well, because data can change between validation and persistence. A dropdown cannot enforce a server invariant: a caller can submit a different identifier directly.

Test three selection states

Use a local copy to compare a valid country selection, no selection, and a fabricated identifier. Verify the submitted JSON rather than just the label on screen. If you later add an edit form, load the saved identifier and options before setting the control value so the selected label can be resolved.

Also consider a previously selected country that becomes soft-deleted. The normal lookup query may hide it while an older Currency still references it. Decide how the edit screen should display that historical association. Silently clearing the relationship because the option is absent would change data without the user intending it.

Key Takeaways

  • Display labels and persisted identifiers have different purposes.
  • Entity relationships and dropdown wiring are separate layers.
  • Validate submitted identifiers even when the UI offers a controlled list.

FAQ

Should the form submit the country name?

Submit the stable country identifier and display the name as its label.

Does WithMany require a collection property?

No. The configuration can represent the relationship without an inverse collection navigation.

Does an optional dropdown allow any string?

No. Optional means the relationship can be absent; a supplied identifier still needs to identify a valid related record.