Code-First Entity Framework с сохраненной процедурой, возвращающей результаты сложных полнотекстовых поисков - программирование
Подтвердить что ты не робот

Code-First Entity Framework с сохраненной процедурой, возвращающей результаты сложных полнотекстовых поисков

Я ищу совет по дизайну для следующего сценария:

У меня есть первое приложение EF5 MVC с кодом. Я создаю полнотекстовую функцию поиска, которая будет включать несколько взвешенных столбцов из многих таблиц. Поскольку я не могу создать представление с индексом из этих таблиц (некоторые из них содержат текстовые/двоичные столбцы), я создал хранимую процедуру, которая выведет идентификатор моего объекта (например, PersonID) и ранг, связанный с этим объектом на основе условий поиска.

Мой текущий подход заключается в создании вспомогательного класса для выполнения полнотекстовых поисков, которые вызывают хранимую процедуру и загружают все объекты из контекста на основе возвращенных идентификаторов.

Мои вопросы:

  • Является ли мой подход разумным/следует разумной лучшей практике?
  • Кто-нибудь еще сделал что-то подобное с любыми извлеченными уроками?
  • Есть ли способ сделать это более эффективно (т.е. получить результаты возвращенной/сопоставленной хранимой процедуры для объектов непосредственно без дополнительного поиска?)

UPDATE

Переместил мою детальную реализацию из редактирования вопроса в свой собственный ответ, чтобы больше соответствовать тому, что рекомендуется часто @meta.stackexchange.com

4b9b3361

Ответ 1

Проводя это как ответ, а не редактирование на мой вопрос:

Взяв некоторую информацию, предоставленную @Drauka (и google), вот что я сделал для своей начальной итерации.

  • Создал хранимую процедуру для полнотекстового поиска. Это было слишком сложно сделать в EF, даже если поддерживается (в качестве одного из примеров некоторые из моих сущностей связаны с бизнес-логикой, и я хотел бы сгруппировать их, возвращаясь как единственный результат). Хранимая процедура сопоставляется с DTO с идентификатором объекта и рангом.
  • Я изменил этот фрагмент блога/код, чтобы сделать вызов хранимой процедуры, и заполнить мой DTO: http://www.lucbos.net/2012/03/calling-stored-procedure-with-entity.html
  • Я заполняю свой объект результатов итогами и информацией поискового вызова из результатов хранимой процедуры, а затем просто загружаю объекты для текущей страницы результатов:

    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> {
    }
}

Класс для хранения сохраненных входов proc:

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

    }

Ответ 2

  • Увидев, что вы не можете использовать SQL-методы, такие как сдерживаемый с кодом сущности, который в первую очередь может использовать остальная часть вашего приложения, может быть "принудительным" для выполнения чего-либо с сохраненной процедурой, подобной описанию. Я не знаю, насколько это лучше. Однако он выполняет эту работу. Я не понимаю, почему это было бы разумно.
  • Да. Я все еще работаю над созданием проекта вокруг EF codefirst, где мне нужно было выполнить довольно сложный поиск, включающий несколько параметров поиска, отмеченных как "must have", и несколько значений, помеченных как "приятно иметь" и в от этого возвращают взвешенный результат.
  • В зависимости от сложности набора результатов я не думаю, что вам нужно сделать второй тур в базу данных, и я покажу вам, как я это делал ниже.

Имейте в виду, что ниже это просто пример:

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

Несмотря на то, что хранимая процедура будет иметь столбец для значения веса, он не будет отображаться при его переводе. Вы могли бы потенциально получить класс от Лица, который включает значение веса, если вам это нужно.