Skip to main content

One of the advanced features of EF Core is the ability to use shadow properties. In this article, we'll explore what shadow properties are, how they work, and their limitations, particularly in the context of entity relationships. We'll also cover how to configure shadow entity relationships where only entity IDs are defined in related entities.

# Understanding Shadow Properties and Relationships in EF Core

Entity Framework Core (EF Core) is a powerful Object-Relational Mapper (ORM) for .NET that allows developers to work with databases using .NET objects. One of the advanced features of EF Core is the ability to use shadow properties. In this article, we'll explore what shadow properties are, how they work, and their limitations, particularly in the context of entity relationships. We'll also cover how to configure shadow entity relationships where only entity IDs are defined in related entities.

## What are Shadow Properties?

Shadow properties are properties that are not defined in your entity classes but are defined in the EF Core model. These properties are useful when you need to store additional data in the database without cluttering your domain models with extra properties.

### Defining Shadow Properties

Shadow properties are defined in the `OnModelCreating` method of your `DbContext` using the `ModelBuilder`. Here's an example:

```csharp
public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public DateTime OrderDate { get; set; }
    // Other properties...
}

public class ApplicationDbContext : DbContext
{
    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Order>()
            .Property<DateTime>("CreatedDate");
    }
}
```

In this example, the `CreatedDate` property is not defined in the `Order` class but is configured as a shadow property in the `OnModelCreating` method.

### Using Shadow Properties

You can access and manipulate shadow properties using the `ChangeTracker` API or LINQ queries. Here's how you can set a shadow property:

```csharp
using (var context = new ApplicationDbContext())
{
    var order = new Order { CustomerId = 1, OrderDate = DateTime.Now };
    context.Orders.Add(order);
    context.Entry(order).Property("CreatedDate").CurrentValue = DateTime.Now;
    context.SaveChanges();
}
```

## Shadow Properties and Relationships

While shadow properties are great for storing additional scalar data, they cannot be used for defining relationships or collections of related entities. For relationships, you need to define navigation properties in your entity classes.

### Example with Navigation Properties

Let's consider an example where we have `Order` and `Customer` entities with a relationship between them:

```csharp
public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public DateTime OrderDate { get; set; }
    public Customer Customer { get; set; } // Navigation property
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Order> Orders { get; set; } // Navigation property
}
```

In the `DbContext`, we configure the relationship:

```csharp
public class ApplicationDbContext : DbContext
{
    public DbSet<Order> Orders { get; set; }
    public DbSet<Customer> Customers { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Order>()
            .HasOne(o => o.Customer)
            .WithMany(c => c.Orders)
            .HasForeignKey(o => o.CustomerId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}
```

In this setup:

- The `Order` entity has a navigation property to the `Customer` entity.
- The `Customer` entity has a collection navigation property to the `Order` entities.
- The relationship is configured using the `HasOne` and `WithMany` methods.

## Configuring Shadow Entity Relationships

Sometimes, you may want to configure relationships where entities' IDs are the only properties defined in related entities, without navigation properties. Here's how you can achieve that:

### Define the Entities

```csharp
public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public DateTime OrderDate { get; set; }
    // Other properties...
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    // Other properties...
}
```

### Configure the Relationship in the `DbContext`

```csharp
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : DbContext
{
    public DbSet<Order> Orders { get; set; }
    public DbSet<Customer> Customers { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Order>()
            .HasOne<Customer>()
            .WithMany()
            .HasForeignKey(o => o.CustomerId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}
```

In this configuration:

- The `Order` entity has a `CustomerId` property that serves as a foreign key.
- The `Customer` entity does not have a navigation property to `Order`.
- The relationship is configured using the `HasOne` and `WithMany` methods.
- The `OnDelete` method specifies the delete behavior for the relationship.

This setup allows you to work with rich domain models while keeping the relationships defined only by the foreign key properties.

## Conclusion

Shadow properties in EF Core are a powerful feature for storing additional scalar data without modifying your domain models. However, they are not suitable for defining relationships or collections of related entities. For relationships, you need to use navigation properties in your entity classes. Understanding the appropriate use cases for shadow properties and navigation properties will help you design cleaner and more maintainable EF Core models. Additionally, configuring shadow entity relationships where only entity IDs are defined can help maintain a clean and efficient domain model.