You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core TPC映射下如何查询带MobileRefreshToken的用户?

问题描述

我是ASP.NET和EF Core新手,对EF Core中继承和导航属性的工作机制存在误解。我有一个关联用户外键的RefreshToken表,包含两种类型:仅专业用户可使用的WEB类型,以及普通用户和专业用户均可使用的MOBILE类型。

public class RefreshToken
{
    [Key] [StringLength(64)] public string Id { get; set; }
    [StringLength(100)] public string Token { get; set; }
    public DateTime ExpiresAt { get; set; }
    public RefreshTokenType Type { get; set; } // WEB or MOBILE

    // Foreign key for user
    [StringLength(64)] public string UserId { get; set; }
    // Navigation property for user
    public BaseUserModel User { get; set; }
}

BaseUserModel是抽象类,由UserModel和ProUserModel两个具体用户类型继承。采用TPC映射,希望拥有两个独立的用户表。在类中添加了导航属性以便获取所需的刷新令牌,但遇到了问题。

父类代码

public abstract class BaseUserModel
{
    [Key]
    public string UserId { get; set; }

    [Column("phone_number")]
    public string PhoneNumber { get; set; }

    // navigation properties
    protected internal virtual ICollection<RefreshToken>? RefreshTokens { get; set; }
    public RefreshToken? MobileRefreshToken =>
        RefreshTokens?.FirstOrDefault(rt => rt.Type == RefreshTokenType.MobileApp);
}

子类代码

public class ProUserModel : BaseUserModel
{
    [Column("email")] 
    public string Email { get; set; }

    public RefreshToken? WebRefreshToken => RefreshTokens?.FirstOrDefault(rt => rt.Type == RefreshTokenType.WebApp);
}

public class UserModel : BaseUserModel
{
    [Column("address")] 
    public string Address { get; set; }
}

我希望通过user_id查询任意类型的用户,并同时获取其MobileRefreshToken,但尝试多种方法均失败:


尝试的方法及错误

方法一

private async Task<BaseUserModel?> GetUserWithTokenById(string userId)
{
    return await dbContext.BaseUsers.Include(u => u.MobileRefreshToken).SingleOrDefaultAsync(u => u.UserId == userId);
}

报错信息:

System.InvalidOperationException: The expression 'u.MobileRefreshToken' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. 

方法二

private async Task<BaseUserModel?> GetUserWithTokenById(string userId)
{
    var proUser = await dbContext.ProUsers.Include(u => u.MobileRefreshToken).SingleOrDefaultAsync(u => u.UserId == userId);
    if (proUser != null)
    {
        return proUser;
    }
    return await dbContext.Users.Include(u => u.MobileRefreshToken).SingleOrDefaultAsync(u => u.UserId == userId);
}

仍出现相同错误。

方法三

var user = await dbContext.Users
    .SingleOrDefaultAsync(u => u.UserId == userId);
if (user != null)
{
    await dbContext.Entry(user)
        .Reference(u => u.MobileRefreshToken)
        .LoadAsync();
    return user;
}
// Same for pro user if null

报错信息:

System.InvalidOperationException: The property 'UserModel.MobileRefreshToken' could not be found. Ensure that the property exists and has been included in the model.

解决方案

核心问题是:MobileRefreshToken不是EF Core识别的导航属性,它只是基于RefreshTokens集合计算出的只读属性,EF无法对这类计算属性执行Include或显式加载操作。正确做法是直接操作底层的RefreshTokens导航属性:

方案1:加载完整RefreshTokens集合

直接IncludeRefreshTokens,之后计算属性MobileRefreshToken可正常工作:

private async Task<BaseUserModel?> GetUserWithTokenById(string userId)
{
    // 先查询专业用户
    var proUser = await dbContext.ProUsers
        .Include(u => u.RefreshTokens)
        .SingleOrDefaultAsync(u => u.UserId == userId);
    if (proUser != null)
        return proUser;
    
    // 再查询普通用户
    return await dbContext.Users
        .Include(u => u.RefreshTokens)
        .SingleOrDefaultAsync(u => u.UserId == userId);
}

方案2:过滤Include优化性能

如果不想加载所有令牌,可使用EF Core的过滤Include功能,只加载MobileApp类型的令牌:

private async Task<BaseUserModel?> GetUserWithTokenById(string userId)
{
    var proUser = await dbContext.ProUsers
        .Include(u => u.RefreshTokens.Where(rt => rt.Type == RefreshTokenType.MobileApp))
        .SingleOrDefaultAsync(u => u.UserId == userId);
    if (proUser != null)
        return proUser;
    
    return await dbContext.Users
        .Include(u => u.RefreshTokens.Where(rt => rt.Type == RefreshTokenType.MobileApp))
        .SingleOrDefaultAsync(u => u.UserId == userId);
}

补充说明

  • 计算属性(如MobileRefreshToken)不会被EF映射到数据库,只能在内存中基于已加载的导航属性计算;
  • TPC模式下,抽象类BaseUserModel不会生成数据库表,必须通过具体的ProUsers和Users DbSet查询;
  • 两次查询的方式逻辑清晰,实际性能影响极小,若想避免两次查询,可通过SQL直接查询合并结果,但必要性不高。

内容的提问来源于stack exchange,提问作者Matthias64

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 08:33:25