简体   繁体   中英

Entity Framework database tables association

I have a domain model like this:

public class Rental
{
    public int Id { get; set; }

    public DateTime DateRented { get; set; }
    public DateTime? DateReturned { get; set; }

    [Required]
    public Customer Customer { get; set; }

    [Required]
    public Movie Movie { get; set; }
}

Entity Framework associated that with two other tables ( Customers and Movies ) and automatically created foreign key columns in Rentals table. In my .NET course I was taught to make associations with other tables with the addition of an Id property, so for instance the Rental domain model would contain these properties:

public int MovieId { get; set; }
public int CustomerId { get; set; }

But the code works well without these lines of code. Can you explain to me which approach is better? Or maybe one of them gives more flexibility? I'm new to .NET so every explanation would be welcomed.

First below lines will throw you a syntax error, that's not valid c# code

public MovieId { get; set; }
public CustomerId { get; set; }

The second thing EF is smart enough to know the relation between entities, and from use case point of you, it's always better to put Typed property that way when you want to access the information of about customer(Eg Name) you have it really available using rental.Customer.Name and you don't have to pull it separately.

When using Code-First where EF is defining your schema, EF will use conventions to create suitable FK columns in the tables and use those behind the scenes. You can define your own FK with whatever naming convention you want, but you will need to tell EF what column to use as the FK relationship for what table. This is done by using the [ForeignKey] attribute or configuring the relationship (modelBuilder or EntityTypeConfiguration ) with the foreign key.

By convention EF will define the FK column name by the Type of the related object, which may not be what you want in the schema. For example if you have an Order entity which you want references to a User entity for CreatedBy and LastModifiedBy, you might expect to have something like

public class Order
{
    // ...
    public virtual User CreatedBy { get; set; }
    public virtual User LastModifiedBy { get; set; }
}

EF would create FK as something like: User_Id and User_Id2

This can trip up people that want to include the FK in the entity:

public class Order
{
    // ...
    public int CreatedByUserId { get; set; }
    public virtual User CreatedBy { get; set; }
    public int LastModifiedByUserId { get; set; }
    public virtual User LastModifiedBy { get; set; }
}

... expecting that to work then wondering why the IDs in the table aren't being populated.

To use more meaningful FKs you need to configure them:

public class Order
{
    // ...
    [ForeignKey("CreatedBy")]
    public int CreatedByUserId { get; set; }
    public virtual User CreatedBy { get; set; }
    [ForeignKey("LastModifiedBy")]
    public int LastModifiedByUserId { get; set; }
    public virtual User LastModifiedBy { get; set; }
}

The ForeignKey attribute can be put on either the FK pointing at the navigation property or on the navigation property pointing back to the FK.

However, I generally recommend not defining FKs in the entity as this can lead to potential issues when updating references as there are 2 sources of truth for the relationship.

Do I use order.LastModifiedByUserId or order.LastModifiedBy.UserId ? What if I want to update a user and some code references one or the other of those? If I change the User navigation property when does the FK on Order get updated? What if I set the FK and not the navigation property?

If I want to update the LastModifiedBy for an order:

I might be tempted to do this:

var order = context.Orders.Single(x => x.OrderId == orderId);
order.LastModifiedByUserId = currentUserId;

However, I should do this:

var currentUser = context.Users.Single(x => x.UserId == currentUserId);
var order = context.Orders.Single(x => x.OrderId == orderId);
order.LastModifiedBy = currentUser;

Depending on whether the LastModifiedBy navigation property was eager loaded, or the DBContext could populate it (because it was already loaded and could be populated when saturating the order entity) will determine the behaviour of trying to update the FK. The second option is the consistent one, however note that even there, any FK on Order will not be updated automatically until SaveChanges is called.

Generally it is a better idea to avoid exposing FKs in the entity at all. For EF6 this can be accomplished by using Map.MapKey , where EF Core supports Shadow Properties. For example, to configure the following entity to define the FK column name to use, but avoid exposing a FK property:

public class Order
{
    // ...
    public virtual User CreatedBy { get; set; }
    public virtual User LastModifiedBy { get; set; }
}

Using modelBuilder or EntityTypeConfiguration

EF6

.HasRequired(x => x.CreatedBy)
    .WithMany()
    .Map(x => x.MapKey("CreatedByUserId"));
.HasRequired(x => x.LastModifiedBy)
    .WithMany()
    .Map(x => x.MapKey("LastModifiedByUserId"));

EF Core

.HasOne(x => x.CreatedBy)
    .WithMany()
    .HasForeignKey("CreatedByUserId");
.HasOne(x => x.LastModifiedBy)
    .WithMany()
    .HasForeignKey("LastModifiedByUserId");

/w EF Core the HasForeignKey can be used to define a shadow property for the FK column rather than point at an exposed property in the entity. The advantage here is that there is no longer two sources of truth for the related entity's key.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM