16

我正在寻找以下场景的设计建议:

我有一个代码优先的 EF5 MVC 应用程序。我正在构建一个全文搜索功能,它将包含来自许多表的多个加权列。由于我无法使用这些表中的索引创建视图(其中一些包含文本/二进制列),因此我创建了一个存储过程,它将输出我的对象的 ID(例如PersonID)以及与该对象关联的排名搜索词。

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

我的问题是:

  1. 我的方法是否合理/遵循合理的最佳实践?
  2. 有没有其他人在吸取任何教训后做过类似的事情?
  3. 有没有办法更有效地做到这一点(即存储过程的结果直接返回/映射到实体而不需要额外的查找?)

更新

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

4

2 回答 2

13
  1. 看到你不能首先使用包含实体框架代码的 SQL 方法,你的应用程序的其余部分可能会使用它,你可能会“被迫”用你描述的存储过程做一些事情。我不知道这是否是最佳实践。但是它完成了工作,我不明白为什么它不明智。
  2. 是的 - 我已经并且仍在研究围绕 EF codefirst 构建的项目,在该项目中我必须进行相当复杂的搜索,其中包括几个标记为“必须拥有”的搜索参数和几个标记为“很好拥有”的值,并且来自该返回加权结果。
  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 派生一个包含权重值的类。

于 2013-01-09T22:17:39.103 回答
11

将其发布为答案而不是对我的问题的编辑:

在这里获取@Drauka(和谷歌)提供的一些见解是我为我的初始迭代所做的。

  1. 创建了存储过程来进行全文搜索。即使支持,在 EF 中完成也确实太复杂了(作为一个示例,我的一些实体通过业务逻辑相关,我想将它们分组为单个结果返回)。存储过程映射到具有实体 id 和 Rank 的 DTO。
  2. 我修改了这个博主的片段/代码来调用存储过程,并填充我的 DTO:http ://www.lucbos.net/2012/03/calling-stored-procedure-with-entity.html
  3. 我用存储过程的结果中的总计和分页信息填充我的结果对象,然后只加载当前结果页面的实体:

    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));
    

全面实施

由于这个问题收到了很多意见,我认为可能值得发布我的最终解决方案的更多细节以供其他人帮助或可能的改进。

完整的解决方案如下所示:

数据库扩展类:

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();

    }
于 2014-02-19T22:55:41.183 回答