Search code examples
c#entity-frameworkgenericsfluentmethod-chaining

How type parameters are passed to next method


Given the following method-chain:

    modelBuilder
        .Entity<Student>()
        .HasOne<StudentAddress>(s => s.Address)
        .WithOne(ad => ad.Student);

How does HasOne know about the Student type (shown in lambda argument) provided by its previous method Entity<Student>?

Seems to me as if Entity<Student> somehow passes its designated type parameter Student to the next method via chaining (return value of Entity<Student> being the base object for next method HasOne).


Solution

  • I'm guessing that you are calling this method:

    public virtual EntityTypeBuilder<TEntity> Entity<TEntity>() where TEntity : class;
    

    Look closely at the signature. The method takes a generic parameter called TEntity, and returns a EntityTypeBuilder<TEntity>, allowing you to chain any calls that can be called on EntityTypeBuilder<TEntity> at the end of a call to Entity<TEntity>() call.

    In your case, you called Entity<Student>(), so the signature dictates that the result must be a EntityTypeBuilder<Student>. Then, you were able to call EntityTypeBuilder<Student>.HasOne:

    public virtual ReferenceNavigationBuilder<TEntity,TRelatedEntity> HasOne<TRelatedEntity> (Expression<Func<TEntity,TRelatedEntity>> navigationExpression = null) where TRelatedEntity : class;
    

    Look at what HasOne accepts - Expression<Func<TEntity,TRelatedEntity>>. Because you are calling EntityTypeBuilder<Student>.HasOne, TEntity is Student. The compiler sees your lambda expression and infers that s must be Student, because that's the only way that the lambda expression could be converted to Expression<Func<TEntity,TRelatedEntity>>.

    Also note that HasOne returns a ReferenceNavigationBuilder<TEntity,TRelatedEntity>, which allows you to chain other calls, and that now you have passed two pieces of type information TEntity and TRelatedEntity.

    In fact, you don't need to specify the generic parameter for HasOne, the compiler can infer those as well:

    modelBuilder
        .Entity<Student>()
        .HasOne(s => s.Address)
        .WithOne(ad => ad.Student);