如何在 EF Core 5 中配置自定义 SQL 的导航属性

2024-05-04

我有一个自定义 SQL 语句来获取客户的最大订单。我没有名为MaxOrders- 这只是一个自定义查询。

我正在使用以下方式获取客户记录和相关对象Include

dbcontext.Customers.Include(x => x.MaxOrder)

我想知道如何为这种情况配置导航属性。

客户等级

public class Customer 
{
    public int Id { get; set;}
    public string Name { get; set;}

    public MaxOrder MaxOrder { get; set;}
}

最大订单类

public class MaxOrder 
{
    public int CustomerId { get; set;}
    public decimal TotalAmount { get; set;}

    public Customer Customer { get; set;}
}

数据库上下文

public DbSet<Customer> Customers { get; set; }
public DbSet<MaxOrder> MaxOrders{ get; set; }

模型构建器

modelBuilder.Entity<MaxOrder>()
            .HasNoKey()
            .ToView(null)
            .ToSqlQuery(@"SELECT CustomerId, SUM(Amount) AS TotalAmount 
                          FROM Orders O 
                          WHERE Id = (SELECT MAX(Id) 
                                      FROM Orders 
                                      WHERE CustomerId = O.CustomerId)
                          GROUP BY CustomerId")

免责声明:你要问的是notEF Core 5.0 自然支持,因此提供的解决方法很可能会在未来的 EF Core 版本中崩溃。使用它需要您自担风险,或者使用什么is支持(映射到real包含所需 SQL 的数据库视图,正如其他人提到的)。

现在,问题来了。首先,您想要映射到 SQL 并在关系中使用的实体类型cannot无钥匙。原因很简单,因为目前无键实体类型 https://learn.microsoft.com/en-us/ef/core/modeling/keyless-entity-types?tabs=data-annotations#keyless-entity-types-characteristics

仅支持导航映射功能的子集,具体来说:

  • 他们可能永远不会成为一段关系的主要目的。
  • 他们可能没有导航到拥有的实体
  • 它们只能包含指向常规实体的引用导航属性。
  • 实体不能包含无键实体类型的导航属性。

就你而言,Customer通过将导航属性定义为无键实体,违反了最后一条规则。但没有它你将无法使用Include,这是这一切的最终目标。

该限制没有解决方法。即使使用一些黑客手段映射关系并获得正确的 SQL 翻译,导航属性仍然​​不会被加载,因为所有 EF Core 相关的数据加载方法都依赖于更改跟踪,并且它需要带有键的实体。

因此,该实体必须是“正常的”(带有密钥)。这没有问题,因为查询具有定义一对一关系的唯一列。然而,这遇到了当前 EF Core 的另一个限制 - 您会得到NotImplemented在模型最终确定期间映射到 SqlQuery 的正常实体的异常。不幸的是这是在里面static关系模型终结里面很多地方都会用到这个函数,这也是一个static方法,因此实际上不可能从外部拦截和修复它。

一旦您了解了问题(支持什么、不支持什么),下面就是解决方法。支持的映射是要查看的普通实体。所以我们将使用它(ToView而不是失败ToSqlQuery),但将提供包含在其中的 SQL,而不是名称()能够从关联的 EF Core 元数据中识别并提取它。请注意,EF Core 不会验证/关心您在中提供的名称是什么ToTable and ToView方法 - 只是它们是否是null or not.

然后我们需要插入 EF Core 查询处理管道并将“视图名称”替换为实际的 SQL。

以下是上述想法的实现(将其放入 EF Core 项目内的某个代码文件中):

namespace Microsoft.EntityFrameworkCore
{
    using Metadata.Builders;
    using Query;

    public static class InlineSqlViewSupport
    {
        public static DbContextOptionsBuilder AddInlineSqlViewSupport(this DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder.ReplaceService<ISqlExpressionFactory, CustomSqlExpressionFactory>();

        public static EntityTypeBuilder<TEntity> ToInlineView<TEntity>(this EntityTypeBuilder<TEntity> entityTypeBuilder, string sql)
            where TEntity : class => entityTypeBuilder.ToView($"({sql})");
    }
}

namespace Microsoft.EntityFrameworkCore.Query
{
    using System.Linq.Expressions;
    using Metadata;
    using SqlExpressions;

    public class CustomSqlExpressionFactory : SqlExpressionFactory
    {
        public override SelectExpression Select(IEntityType entityType)
        {
            var viewName = entityType.GetViewName();
            if (viewName != null && viewName.StartsWith("(") && viewName.EndsWith(")"))
            {
                var sql = viewName.Substring(1, viewName.Length - 2);
                return Select(entityType, new FromSqlExpression("q", sql, NoArgs));
            }
            return base.Select(entityType);
        }

        private static readonly Expression NoArgs = Expression.Constant(new object[0]);

        public CustomSqlExpressionFactory(SqlExpressionFactoryDependencies dependencies) : base(dependencies) { }
    }
}

前两种方法只是为了方便 - 一种用于添加必要的管道,一种用于在名称中编码 SQL。 实际工作在第三个类中,它替换了标准 EF Core 服务之一,拦截Select方法负责表/视图/TVF表达式映射,并将特殊视图名称转换为SQL查询。

有了这些助手,您就可以使用示例模型并DbSet就这样。您所需要做的就是将以下内容添加到您的派生中DbContext class:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    // ...
    optionsBuilder.AddInlineSqlViewSupport(); // <--
}

并使用以下流畅的配置:


modelBuilder.Entity<MaxOrder>(builder =>
{
    builder.HasKey(e => e.CustomerId);
    builder.ToInlineView(
        @"SELECT CustomerId, SUM(Amount) AS TotalAmount 
          FROM Orders O 
          WHERE Id = (SELECT MAX(Id) 
                      FROM Orders 
                      WHERE CustomerId = O.CustomerId)
        GROUP BY CustomerId");
});

Now

var test = dbContext.Customers
    .Include(x => x.MaxOrder)
    .ToList();

将运行无错误并生成 SQL

SELECT [c].[Id], [c].[Name], [q].[CustomerId], [q].[TotalAmount]
FROM [Customers] AS [c]
LEFT JOIN (
    SELECT CustomerId, SUM(Amount) AS TotalAmount 
                          FROM Orders O 
                          WHERE Id = (SELECT MAX(Id) 
                                      FROM Orders 
                                      WHERE CustomerId = O.CustomerId)
                        GROUP BY CustomerId
) AS [q] ON [c].[Id] = [q].[CustomerId]

更重要的是,将正确填充Customer.MaxOrder财产。任务完成:)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 EF Core 5 中配置自定义 SQL 的导航属性 的相关文章

  • C# 和 Javascript SHA256 哈希的代码示例

    我有一个在服务器端运行的 C 算法 它对 Base64 编码的字符串进行哈希处理 byte salt Convert FromBase64String serverSalt Step 1 SHA256Managed sha256 new S
  • UML类图:抽象方法和属性是这样写的吗?

    当我第一次为一个小型 C 项目创建 uml 类图时 我在属性方面遇到了一些麻烦 最后我只是将属性添加为变量 lt
  • 如何避免情绪低落?

    我有一个实现状态模式每个状态处理从事件队列获取的事件 根据State因此类有一个纯虚方法void handleEvent const Event 事件继承基础Event类 但每个事件都包含其可以是不同类型的数据 例如 int string
  • 如何在列表框项目之间画一条线

    我希望能够用水平线分隔列表框中的每个项目 这只是我用于绘制项目的一些代码 private void symptomsList DrawItem object sender System Windows Forms DrawItemEvent
  • 仅具有存储过程的实体框架

    我对在我们的场景中仅使用实体框架与存储过程的合理性有疑问 我们计划拥有一个 N 层架构 包括 UI BusinessLayer BLL DataAccessLayer DAL 和 BusinessObjectDefinitions BOD
  • 将布尔参数传递给 SQL Server 存储过程

    我早些时候问过这个问题 我以为我找到了问题所在 但我没有 我在将布尔参数传递给存储过程时遇到问题 这是我的 C 代码 public bool upload false protected void showDate object sende
  • WPF 中的调度程序和异步等待

    我正在尝试学习 WPF C 中的异步编程 但我陷入了异步编程和使用调度程序的困境 它们是不同的还是在相同的场景中使用 我愿意简短地回答这个问题 以免含糊不清 因为我知道我混淆了 WPF 中的概念和函数 但还不足以在功能上正确使用它 我在这里
  • 在 Visual Studio 2008 上设置预调试事件

    我想在 Visual Studio 中开始调试程序之前运行一个任务 我每次调试程序时都需要运行此任务 因此构建后事件还不够好 我查看了设置的 调试 选项卡 但没有这样的选项 有什么办法可以做到这一点吗 你唯一可以尝试的 IMO 就是尝试Co
  • C#:如何防止主窗体过早显示

    在我的 main 方法中 我像往常一样启动主窗体 Application EnableVisualStyles Application SetCompatibleTextRenderingDefault false Application
  • Qt moc 在头文件中实现?

    是否可以告诉 Qt MOC 我想声明该类并在单个文件中实现它 而不是将它们拆分为 h 和 cpp 文件 如果要在 cpp 文件中声明并实现 QObject 子类 则必须手动包含 moc 文件 例如 文件main cpp struct Sub
  • Web API - 访问 DbContext 类中的 HttpContext

    在我的 C Web API 应用程序中 我添加了CreatedDate and CreatedBy所有表中的列 现在 每当在任何表中添加新记录时 我想填充这些列 为此目的我已经覆盖SaveChanges and SaveChangesAsy
  • 指针减法混乱

    当我们从另一个指针中减去一个指针时 差值不等于它们相距多少字节 而是等于它们相距多少个整数 如果指向整数 为什么这样 这个想法是你指向内存块 06 07 08 09 10 11 mem 18 24 17 53 7 14 data 如果你有i
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • 如何衡量两个字符串之间的相似度? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 给定两个字符串text1 and text2 public SOMEUSABLERETURNTYPE Compare string t
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 如何让Gtk+窗口背景透明?

    我想让 Gtk 窗口的背景透明 以便只有窗口中的小部件可见 我找到了一些教程 http mikehearn wordpress com 2006 03 26 gtk windows with alpha channels https web
  • const、span 和迭代器的问题

    我尝试编写一个按索引迭代容器的迭代器 AIt and a const It两者都允许更改容器的内容 AConst it and a const Const it两者都禁止更改容器的内容 之后 我尝试写一个span
  • x86 上未对齐的指针

    有人可以提供一个示例 将指针从一种类型转换为另一种类型由于未对齐而失败吗 在评论中这个答案 https stackoverflow com questions 544928 reading integer size bytes from a
  • 如何使用 std::string 将所有出现的一个字符替换为两个字符?

    有没有一种简单的方法来替换所有出现的 in a std string with 转义 a 中的所有斜杠std string 完成此操作的最简单方法可能是boost字符串算法库 http www boost org doc libs 1 46
  • 防止索引超出范围错误

    我想编写对某些条件的检查 而不必使用 try catch 并且我想避免出现 Index Out of Range 错误的可能性 if array Element 0 Object Length gt 0 array Element 1 Ob

随机推荐