Как улучшить структуру Entity Framework и взаимодействие с Javascript

Это довольно неопределенный/субъективный вопрос. Я хочу знать, является ли это лучшим способом отправки/получения данных в/из браузера с помощью вызовов ajax. На веб-сервере back end я хочу использовать инфраструктуру entity. Ниже приведены две примерные функции.

Критерии "наилучшего" - это скорость написания кода, читаемого кода и надежной архитектуры.

Спасибо за любые отзывы и предложения и комментарии.

Получить функцию

[WebMethod]
public AjaxEmployee EmployeeGetById(int employeeID, bool getTimeOff)
{
    using (Time_TrackerEntities ctx = new Time_TrackerEntities())
    {
        var results = from item in ctx.Employees
                      where item.ID == employeeID
                      orderby item.Last_Name
                      select new AjaxEmployee
                      {
                          ID = item.ID,
                          Employee_ID = item.Employee_ID,
                          First_Name = item.First_Name,
                          Middle_Name = item.Middle_Name,
                          Last_Name = item.Last_Name,
                          Supervisor_ID = item.Supervisor_ID,
                          Active = item.Active,
                          Is_Supervisor = item.Is_Supervisor
                      };
        var emp = results.FirstOrDefault();
        if (getTimeOff)
        {
            var results2 = from item2 in ctx.Time_Off
                           where item2.Employee_ID == emp.Employee_ID
                           select new AjaxTime_Off
                           {
                               ID = item2.ID,
                               Employee_ID = item2.Employee_ID,
                               Date_Off = item2.Date_Off,
                               Hours = item2.Hours
                           };
            emp.Time_Off = results2.ToList<AjaxTime_Off>();
        }

        return emp;
    }
}

Сохранить функцию

[WebMethod]
public bool EmployeeSave(AjaxEmployee emp)
{
    using (Time_TrackerEntities ctx = new Time_TrackerEntities())
    {
        var results = from item in ctx.Employees
                      where item.ID == emp.ID
                      select item;

        var myEmp = results.FirstOrDefault();
        if (myEmp == null)
        {
            myEmp = new Employee();
            ctx.Employees.AddObject(myEmp);
        }

        myEmp.Employee_ID = emp.Employee_ID;
        myEmp.First_Name = emp.First_Name;
        myEmp.Middle_Name = emp.Middle_Name;
        myEmp.Last_Name = emp.Last_Name;
        myEmp.Supervisor_ID = emp.Supervisor_ID;
        myEmp.Active = emp.Active;
        myEmp.Is_Supervisor = emp.Is_Supervisor;

        return ctx.SaveChanges() > 0;
    }
}

Ответ 1

Есть несколько улучшений.

Сохранить() Метод - не копировать слева направо, использовать встроенную логику EF

Вместо этого:

myEmp.Employee_ID = emp.Employee_ID;
myEmp.First_Name = emp.First_Name;
myEmp.Middle_Name = emp.Middle_Name;
myEmp.Last_Name = emp.Last_Name;
myEmp.Supervisor_ID = emp.Supervisor_ID;
myEmp.Active = emp.Active;
myEmp.Is_Supervisor = emp.Is_Supervisor;

Вы можете сделать это:

ctx.Employees.ApplyCurrentValues(emp).

Что это значит, это поиск объекта с одним и тем же ключом на графике (который есть, поскольку вы только что вернули его с помощью FirstOrDefault()), и переопределите скалярные значения с сущностью, в которую вы проходите, - которая точно, что вы делаете.

Таким образом, ваши 7 строк станут 1, плюс, если вы добавите дополнительные скалярные свойства - вам не придется реорганизовывать свой код. Просто помните - работает только для скалярных свойств, а не для навигационных свойств.

Зачем строить запрос для извлечения первичного ключа? Просто используйте предикат для SingleOrDefault()

Вместо этого:

var results = from item in ctx.Employees
              where item.ID == emp.ID
              select item;

var myEmp = results.FirstOrDefault();

Сделайте это:

var myEmp = ctx.Employees.SingleOrDefault(x => x.ID == emp.Id);

Или даже лучше, используйте технику pipe/filter:

var myEmp = ctx.Employees.WithId(emp.Id).SingleOrDefault();

Где WithId - это метод расширения IQueryable<Employee>, который фильтрует запрос на основе предоставленного идентификатора сотрудника. Это позволяет отключить фильтрацию/бизнес-логику от вашего репозитория /DAL. Он должен идти в вашей модели домена, поэтому вы можете иметь хороший свободный API для запроса своих объектов домена через ваш ORM.

Когда вы извлекаете объект через первичный ключ, вы должны всегда использовать SingleOrDefault() или Single(), никогда FirstOrDefault() или First(). Если это первичный ключ, должен быть только один из них, поэтому вы должны выбросить исключение, если существует более одного, что и делает SingleOrDefault(). И как @Shiraz упоминает - ваш FirstOrDefault() приведет к сбою запроса ниже. Вам всегда нужна нулевая проверка при использовании <First/Single>OrDefault().

Те же улучшения могут быть внесены в ваш метод Get.

В целом нет ничего функционально неправильно с вашим кодом - ему просто нужны тонкие улучшения, проверка нулей и обработка исключений.

Единственное функциональное улучшение, которое я рекомендую, - это переустановка кода веб-службы в общий репозиторий. Поскольку код очень тривиален и может быть повторно использован для любого объекта. Веб-служба не должна касаться транзакций, первичного ключа или логики EF. Он также не должен иметь ссылку на EF DLL. Инкапсулируйте эту логику за репозиторием и делегируйте туда логику персистентности (через интерфейс, конечно).

После внесения изменений, упомянутых выше, ваши методы веб-службы должны содержать не более 5-7 строк кода.

У вас слишком много разума в вашем веб-сервисе - он должен быть немым и постоянным невежественным.

Ответ 2

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

Вместо этого, и это независимо от клиента, но оно применимо и к JS-клиенту так же, я стараюсь рассматривать классы контрактов данных как чистые машины для передачи данных (DTO) без какого-либо отображения в EF. Эти классы - это только документы, которые я передаю туда и обратно, тело сообщения, если хотите. Они могут переводиться в команды на моей модели, или они могут использоваться для заполнения запроса или что-то еще, но они не являются самими сущностями.

Это, я считаю, упрощает многое. Когда вы впервые пишете простой сервис, он может чувствовать себя как дополнительный код, но в течение всей жизни он делает вещи более удобными.

И как раз в качестве примечания, вы также должны подумать о том, чтобы отделить свои обязанности немного лучше. Класс веб-службы не должен нести ответственность за непосредственное создание и использование контекста данных, это должно зависеть от интерфейса DAO или репозитория (или службы домена), который обрабатывает все эти вещи для вас (и применяет транзакции по мере необходимости и т.д.).

Ответ 3

Ваш метод get может сработать.

Если эта строка возвращает null:

   var emp = results.FirstOrDefault();

Затем эта строка выйдет из строя с помощью исключения с нулевой ссылкой:

   where item2.Employee_ID == emp.Employee_ID

Я бы также добавил некоторые блоки catch catch с протоколированием ошибок.