繁体   English   中英

Entity Framework Core 在使用自有类型时尝试加入不存在的表

[英]Entity Framework Core tries to join to a non-existent table when using an owned type

我在 ASP.NET Core 3.1 应用程序中使用 Entity Framework Core 5.0(这是将现有 .NET 框架应用程序迁移到 .NET 核心的一部分)。 我有这样定义的 POCO:

public class Message
{
    public Guid MessageId { get; set; }
    public MessageDispatcherRoute RouteInfo { get; set; }
    public MessageDispatcherRoute ReturnRouteInfo { get; set; }
    public List<MessageResponse> Responses { get; set; }
    // other properties
}

public class MessageDispatcherRoute
{
    public string Url { get; set; }
    public string HttpVerb { get; set; }
}

public class MessageResponse
{
    public Guid MessageId { get; set; }
    public int ResponseNumber { get; set; }
    // other properties
}

表架构如下所示(SQL Server 13.0):

CREATE TABLE [Message] (
    [MessageId] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
    [RouteInfo_Url] VARCHAR(255),
    [RouteInfo_HttpVerb] VARCHAR(10),
    [ReturnRouteInfo_Url] VARCHAR(255),
    [ReturnRouteInfo_HttpVerb] VARCHAR(10),
    -- other columns
}
-- MessageResponse is a separate table

根据有关拥有类型的 Microsoft 文档,我应该能够使用显式声明来完成此操作(这是第一个示例),因此我的 model 构建器看起来像这样(当前 state;我仍在迁移站点):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.RemovePluralizingTableNameConvention(); // https://stackoverflow.com/questions/37493095/entity-framework-core-rc2-table-name-pluralization

    modelBuilder.Entity<App>().HasKey(x => x.AppId);
    modelBuilder.Entity<App>().HasMany(x => x.AppSettings);
    modelBuilder.Entity<CustomQueue>().HasKey(x => x.CustomQueueId);
    modelBuilder.Entity<Message>().HasKey(m => m.MessageId);
    modelBuilder.Entity<Message>().OwnsOne(m => m.RouteInfo);
    modelBuilder.Entity<Message>().OwnsOne(m => m.ReturnRouteInfo);
    modelBuilder.Entity<MessageResponse>().HasKey(m => new { m.MessageId, m.ResponseNumber });
    modelBuilder.Entity<Message>().HasMany(m => m.Responses);
}

所有这些都与 OData controller 挂钩:

public class MessagesController : Controller
{
    private readonly IMyContext _context;

    public MessagesController(IMyContext context)
    {
        _context = context;
    }

    [HttpGet]
    [EnableQuery(MaxExpansionDepth = 1)] 
    public IQueryable<Message> Get(ODataQueryOptions options)
    {
        return _context.Messages.AsQueryable().Include(x => x.Responses).AsSingleQuery(); 
    }
}

问题是,当我使用查询字符串$top=5访问 OData 端点时,我得到一个异常说SqlException: Invalid object name 'MessageDispatcherRoute'. 使用 SQL Profiler,我可以看到 EF 正在尝试加入一个不存在的表(注意:为简洁起见,省略了不相关的列)。

exec sp_executesql N'SELECT [t].[MessageId] 
, [m1].[MessageId], [m1].[RouteInfo_HttpVerb], [m1].[RouteInfo_Url]
, [m0].[MessageId], [m2].[MessageId], [m2].[ResponseNumber]
FROM (
    SELECT TOP(@__TypedProperty_0) [m].[MessageId]
    , [m].[ReturnRouteInfo_HttpVerb], [m].[ReturnRouteInfo_Url]
    FROM [Message] AS [m]
    ORDER BY [m].[MessageId]
) AS [t]
LEFT JOIN [MessageDispatcherRoute] AS [m0] ON [t].[MessageId] = [m0].[MessageId]
LEFT JOIN [MessageDispatcherRoute] AS [m1] ON [t].[MessageId] = [m1].[MessageId]
LEFT JOIN [MessageResponse] AS [m2] ON [t].[MessageId] = [m2].[MessageId]
ORDER BY [t].[MessageId], [m0].[MessageId], [m1].[MessageId], [m2].[MessageId], [m2].[ResponseNumber]'
,N'@__TypedProperty_0 int',@__TypedProperty_0=5

我尝试在 model 构建器中显式设置列名,如EF Core 2.2 所示,拥有的实体在多个层次结构中生成为另一个表,但这没有任何改变。

更新:我已经制作了一个控制台应用程序,只有一个精简的 DbContext,上面显示的 3 个 POCO 没有帮助。 如果我将[Owned]添加到 MessageDispatcherRoute class,则会导致非常奇怪的 output:

LEFT JOIN [Message.ReturnRouteInfo#MessageDispatcherRoute] AS [m0] ON [t].[MessageId] = [m0].[MessageID]
LEFT JOIN [Message.RouteInfo#MessageDispatcherRoute] AS [m1] ON [t].[MessageId] = [m1].[MessageID]
LEFT JOIN [Message.ReturnRouteInfo#MessageDispatcherRoute] AS [m2] ON [t].[MessageId] = [m2].[MessageID]

我究竟做错了什么? 谢谢。

问题可能是您在调用.OwnsOne() .WithOwner()之后错过了对 .WithOwner() 的调用。

文档

配置目标实体由该实体拥有(或属于该实体的一部分)的关系。

即使导航属于同一类型,每个所有权关系的目标实体类型也被视为不同的实体类型。 目标实体类型的配置不适用于其他所有权关系的目标实体类型。

对拥有实体的大多数操作都需要通过拥有者实体使用相应的导航来访问它。

调用此方法后,您应该链接调用 WithOwner(String) 以完全配置关系。

所以,在你的情况下,你可以尝试这样的事情:

modelBuilder.Entity<Message>().OwnsOne(m => m.RouteInfo).WithOwner();
modelBuilder.Entity<Message>().OwnsOne(m => m.ReturnRouteInfo).WithOwner();

在此之后您可能需要添加迁移,但我认为您不需要,因为您的数据库架构已经可以了。

在尝试随机事情以查看会发生什么之后,解决方案是将 map 拥有的类型返回到同一个表,即使文档根本没有提供此信息。 事实上,它明确表示只有当拥有的类型在单独的表中时才需要这样做。

modelBuilder.Entity<Message>().OwnsOne(m => m.RouteInfo, mdr => mdr.ToTable(nameof(Message)));
modelBuilder.Entity<Message>().OwnsOne(m => m.ReturnRouteInfo, mdr => mdr.ToTable(nameof(Message)));

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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