问题描述
我花了数小时试图弄清楚如何将全文搜索合并到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;
}