无法将存储过程结果合并到Entity Framework Core中

问题描述

我花了数小时试图弄清楚如何将全文搜索合并到Entity Framework Core中。

我已经放弃了尝试直接合并全文查询的尝试。因此,我硬着头皮添加了两个存储过程。

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.sql(@"CREATE PROCEDURE [dbo].[GetFullTextSearchResults]
    @SearchTerm     nvarchar(4000),@SkipRows       int,@TakeRows       int
AS
BEGIN
    SET NOCOUNT ON;
    SELECT [Articles].[Id],[Articles].[Title],[Articles].[Slug],[Articles].[Description],[Categories].[Title] AS [Category],[Articles].[UserId],[AspNetUsers].[Name] AS [UserName],[Articles].[UtcDateModified]
    FROM [Articles]
    INNER JOIN CONTAINSTABLE([Articles],*,@SearchTerm) [t] ON [Articles].[Id] = [t].[Key]
    INNER JOIN [Subcategories] ON [Subcategories].[Id] = [Articles].[SubcategoryId]
    INNER JOIN [Categories] ON [Categories].[Id] = [Subcategories].[CategoryId]
    LEFT OUTER JOIN [AspNetUsers] ON [AspNetUsers].[Id] = [Articles].[UserId]
    WHERE [Articles].[Approved] = 1
    ORDER BY [t].[Rank] DESC
    OFFSET @SkipRows ROWS
    FETCH NEXT @TakeRows ROWS ONLY;
END");

    migrationBuilder.sql(@"CREATE PROCEDURE [dbo].[GetFullTextSearchCount]
    @SearchTerm      nvarchar(4000)
AS
BEGIN
    SET NOCOUNT ON;
    SELECT COUNT(1)
    FROM [Articles]
    INNER JOIN CONTAINSTABLE([Articles],@SearchTerm) [t] ON [Articles].[Id] = [t].[Key]
    WHERE [Articles].[Approved] = 1
END");

但是,由于返回的列与我的视图模型之一匹配,但与我的实体模型之一匹配,因此我似乎仍然无法合并它。我读到的一个限制是,Entity Framework Core只能检索属于模型一部分的实体类型。 (我需要一些相关数据,而下载整个实体模型将包含太多数据。)

我不确定为什么如此复杂。但是在这一点上,如果我可以执行ADO.NET类型的查询,那么我会为此解决的。

那么,如何从GetFullTextSearchResults检索结果?还有其他选择吗?

解决方法

您可以创建与返回的列匹配的实体类型。

[NotMapped]属性应用于实体类。在您的上下文中包括一个DbSet实体。在EF配置中,将其设置为no key,以便EF从不尝试更新它,并且不分配任何类型的表或视图名称。

modelBuilder.Entity<YourEntityName>().HasNoKey();

您可以使用以下方式检索数据:

var sessions = await _context.PersonSessionPaperRatingStatistics
    .FromSqlInterpolated($"EXECUTE YourStoredProcedureName {Parameter1},{Parameter2}").ToListAsync().ConfigureAwait(true);
,

我为此战斗了几个小时。事实是,没有一种简单的方法可以将即席查询运行到实时类中。

所以我写了一个扩展方法来提供这个。该代码只需访问DbConnect,并使用阅读器和反射来读取结果并填充目标类型。

此方法有一些限制。例如,它忽略任何交易。但是目标很简单,就是返回一些数据。

public static async Task<IEnumerable<T>> SqlQuery<T>(this DbConnection connection,string query,params SqlParameter[] parameters) where T : new()
{
    // TODO: Do we need to close in this case?
    if (connection.State != ConnectionState.Open)
        connection.Open();

    List<T> results = new List<T>();

    using (var cmd = connection.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.CommandType = CommandType.Text;
        foreach (SqlParameter parameter in parameters)
            cmd.Parameters.Add(parameters);

        PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

        using var reader = await cmd.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            T item = Activator.CreateInstance<T>();

            for (int i = 0; i < reader.FieldCount; i++)
            {
                if (!await reader.IsDBNullAsync(i))
                {
                    string name = reader.GetName(i);
                    PropertyInfo property = properties.FirstOrDefault(p => p.Name.Equals(name,StringComparison.OrdinalIgnoreCase));
                    if (property != null && property.CanWrite)
                        property.SetValue(item,reader.GetValue(i));
                }
            }
            results.Add(item);
        }
    }
    return results;
}