使用Code-First Entity Framework中的存储过程返回复杂全文搜索结果

16

我正在寻求以下情景的设计建议:

我有一个基于代码的 EF5 MVC 应用程序。我正在构建一个全文搜索功能,它将从许多表中合并多个加权列。由于我不能从这些表创建带有索引的视图(其中一些包含文本/二进制列),因此我创建了一个存储过程,该存储过程将输出我的对象的 ID(例如 PersonID)和与该对象关联的排名,基于搜索术语。

我目前的方法是创建一个帮助器类来执行全文搜索, 调用存储过程(s),并根据返回的 IDs 从上下文中加载所有对象。

我的问题如下:

  1. 我的方法是否明智/遵循合理的最佳实践?
  2. 是否有其他人做过类似的事情,并获得了任何教训?
  3. 是否有更高效的方法可以直接将存储过程的结果返回/映射到实体,而无需额外的查找?

更新

将我的详细实现从编辑问题中移动到自己的答案中,以更符合 meta.stackexchange.com 经常推荐的标准。

2个回答

13
  1. 由于无法在使用实体框架的Code First中像containstable这样使用SQL方法,而您的应用程序的其余部分可能正在使用该方法,因此您可能需要类似于您描述的存储过程来处理某些内容。我不知道这是否是最佳做法。然而,只要它能完成工作,我认为这是合理的。
  2. 是的-我曾经并且仍然在使用基于EF Code First构建的项目上工作,其中我必须进行相当复杂的搜索,其中包括几个标记为“必须拥有”的搜索参数和几个标记为“好习惯”的值,并从中返回加权结果。
  3. 根据结果集的复杂性,我认为您不需要对数据库进行第二次访问,我将在下面展示一种方法。

请记住,以下仅为示例:

    public List<Person> GetPeople(params string[] p)
    {
        var people = new List<Person>();

        using (var db = new DataContext())
        {
            var context = ((IObjectContextAdapter)db).ObjectContext;

            db.Database.Connection.Open();

            var command = db.Database.Connection.CreateCommand();
            command.CommandText = "SomeStoredProcedureReturningWeightedResultSetOfPeople";
            command.CommandType = System.Data.CommandType.StoredProcedure;

            //Add parameters to command object

            people = context.Translate<Person>(command.ExecuteReader()).ToList();
        }

        return people;
    }

尽管存储过程将有一个权重值列,但在转换时不会映射该列。 如果需要,您可以从 Person 派生一个类,其中包括权重值。


11

我将这篇文章发布为回答而不是编辑我的问题:

借鉴了@Drauka(和谷歌)提供的一些见解,以下是我进行初步迭代所做的事情。

  1. Created the stored procedure to do the full text searching. It was really too complex to be done in EF even if supported (as one example some of my entities are related via business logic and I wanted to group them returning as a single result). The stored procedure maps to a DTO with the entity id's and a Rank.
  2. I modified this blogger's snippet / code to make the call to the stored procedure, and populate my DTO: http://www.lucbos.net/2012/03/calling-stored-procedure-with-entity.html
  3. I populate my results object with totals and paging information from the results of the stored procedure and then just load the entities for the current page of results:

    int[] projectIDs = new int[Settings.Default.ResultsPerPage];
    foreach (ProjectFTS_DTO dto in 
              RankedSearchResults
              .Skip(Settings.Default.ResultsPerPage * (pageNum - 1))
              .Take(Settings.Default.ResultsPerPage)) {
                 projectIDs[index] = dto.ProjectID;
                 index++;
            }
    
    IEnumerable<Project> projects = _repository.Projects
                .Where(o=>projectIDs.Contains(o.ProjectID));
    
完整实现
由于这个问题得到了很多关注,我认为将我的最终解决方案的更多详细信息发布出来可能会对其他人提供帮助或改进。
完整的解决方案如下:
DatabaseExtensions 类:
public static class DatabaseExtensions {
    public static IEnumerable<TResult> ExecuteStoredProcedure<TResult>(
             this Database database, 
             IStoredProcedure<TResult> procedure, 
             string spName) {
        var parameters = CreateSqlParametersFromProperties(procedure);
        var format = CreateSPCommand<TResult>(parameters, spName);
        return database.SqlQuery<TResult>(format, parameters.Cast<object>().ToArray());
    }

    private static List<SqlParameter> CreateSqlParametersFromProperties<TResult>
             (IStoredProcedure<TResult> procedure) {
        var procedureType = procedure.GetType();
        var propertiesOfProcedure = procedureType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        var parameters =
            propertiesOfProcedure.Select(propertyInfo => new SqlParameter(
                    string.Format("@{0}", 
                    (object) propertyInfo.Name), 
                    propertyInfo.GetValue(procedure, new object[] {})))
                .ToList();
        return parameters;
    }

    private static string CreateSPCommand<TResult>(List<SqlParameter> parameters, string spName)
    {
        var name = typeof(TResult).Name;
        string queryString = string.Format("{0}", spName);
        parameters.ForEach(x => queryString = string.Format("{0} {1},", queryString, x.ParameterName));

        return queryString.TrimEnd(',');
    }

    public interface IStoredProcedure<TResult> {
    }
}

用于保存存储过程输入的类:

class AdvancedFTS : 
         DatabaseExtensions.IStoredProcedure<AdvancedFTSDTO> {
    public string SearchText { get; set; }
    public int MinRank { get; set; }
    public bool IncludeTitle { get; set; }
    public bool IncludeDescription { get; set; }
    public int StartYear { get; set; }
    public int EndYear { get; set; }
    public string FilterTags { get; set; }
}

结果对象:

public class ResultsFTSDTO {
    public int ID { get; set; }
    public decimal weightRank { get; set; }
}

最后调用储存过程:

public List<ResultsFTSDTO> getAdvancedFTSResults(
            string searchText, int minRank,
            bool IncludeTitle,
            bool IncludeDescription,
            int StartYear,
            int EndYear,
            string FilterTags) {

        AdvancedFTS sp = new AdvancedFTS() {
            SearchText = searchText,
            MinRank = minRank,
            IncludeTitle=IncludeTitle,
            IncludeDescription=IncludeDescription,
            StartYear=StartYear,
            EndYear = EndYear,
            FilterTags=FilterTags
        };
        IEnumerable<ResultsFTSDTO> resultSet = _context.Database.ExecuteStoredProcedure(sp, "ResultsAdvancedFTS");
        return resultSet.ToList();

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接