Определение Connection String. Добавление логирования запросов

Скачать Viber 21.05.2019
Скачать Viber

Всем доброго времени суток. На связи Алексей Гулынин. В прошлых статьях мы рассмотрели различные подходы по работе с Entity Framework. В данной статье я бы хотел рассказать, как работать с данными в Entity Framework . Рассмотрим следующие операции:

  • Добавление записи
  • Чтение записей
  • Редактирование записи
  • Удаление записи
  • Создадим новый проект. В этот раз тип проекта будет "Приложение Windows Forms":

    Добавим элемент DataGridView на нашу форму. Также добавим 3 кнопки: "Добавление", "Редактирование", "Удаление". Добавим ещё 2 элемента "TextBox", в которые будем выводить информацию о записи, которая в данный момент выделена (эти 2 "TextBox" будут использованы ещё для добавления новой записи). Добавим ещё один "TextBox", в который будет выводиться информация об "ID" записи (это нужно будет для редактирования записи). Также добавим 2 элемента "label". В конечном итоге наша форма будет выглядеть следующим образом:

    Будем использовать подход "Code First". Создадим следующий класс:

    Public class Countries { public int Id { get; set; } public string Country { get; set; } public string Capital { get; set; } }

    Основной код будет в файле "Form1.cs". Сразу приведу весь код, в комментариях он будет подробно рассмотрен:

    Using System; using System.Windows.Forms; using System.Data.Entity; using System.Data.Entity.Migrations; namespace WorkWithDataInEF { public partial class Form1: Form { private MyModel db; public Form1() { // Создаём объект нашего контекста db = new MyModel(); InitializeComponent(); // Загружаем данные из таблицы в кэш db.Countries.Load(); // Привязываем данные к dataGridView dataGridView1.DataSource = db.Countries.Local.ToBindingList(); } // Событие: клик по ячейке таблицы private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e) { // Проверка выборки строк // Если строка не выбрана, то дальше ничего не происходит if (dataGridView1.CurrentRow == null) return; // Получаем выделенную строку и приводим её у типу Countries Countries country = dataGridView1.CurrentRow.DataBoundItem as Countries; // Если мы щёлкаем по пустой строке, то ничего дальше не делаем if (country == null) return; // Выводим данные о стране и её столице в TextBox tb_Country.Text = country.Country; tb_Capital.Text = country.Capital; tB_ID.Text = country.Id.ToString(); } // Добавление записи private void btn_Add_Click(object sender, EventArgs e) { // Проверяем, что в текстовых полях есть данные if (tb_Country.Text == String.Empty || tb_Capital.Text == String.Empty) { MessageBox.Show("Заполните данные о стране!"); return; } // Создаём экземпляр класса Countries, // т.е получаем данные о нашей стране из текстовых полей Countries country = new Countries { Country = tb_Country.Text, Capital = tb_Capital.Text }; // Заносим данные в нашу таблицу db.Countries.Add(country); // Обязательно сохраняем изменения db.SaveChanges(); // Обновляем наш dataGridView, чтобы в нём отобразилась новая страна dataGridView1.Refresh(); // Обнуляем текстовые поля tb_Country.Text = String.Empty; tb_Capital.Text = String.Empty; tB_ID.Text = String.Empty; } // Редактирование записи private void btn_Edit_Click(object sender, EventArgs e) { // Проверяем, что выбрана запись if (tB_ID.Text == String.Empty) return; // Получаем id из текстового поля int id = Convert.ToInt32(tB_ID.Text); // Находим страну по этому id с помощью метода Find() Countries country = db.Countries.Find(id); if (country == null) return; country.Country = tb_Country.Text; country.Capital = tb_Capital.Text; // Добавляем или обновляем запись db.Countries.AddOrUpdate(country); db.SaveChanges(); dataGridView1.Refresh(); } // Удаление записи // Всё аналогично редактирования записи, только используется метод Remove() private void btn_delete_Click(object sender, EventArgs e) { if (tB_ID.Text == String.Empty) return; int id = Convert.ToInt32(tB_ID.Text); Countries country = db.Countries.Find(id); if (country == null) return; country.Country = tb_Country.Text; country.Capital = tb_Capital.Text; db.Countries.Remove(country); db.SaveChanges(); dataGridView1.Refresh(); } } }

    Как мы видим — всё работает.

    Обучение предполагает наличие знаний по работе с ASP.NET MVC в Visual Studio, в противном случае хорошее место для начала обучения ASP.NET MVC Tutorial . Если вы предпочитаете работать с ASP.NET Web Forms, обратите внимание на Getting Started with the Entity Framework и Continuing with the Entity Framework .

    Перед началом удостоверьтесь в том, что у вас установлено следующее ПО:

    • Visual Studio 2010 SP1 или Visual Web Developer Express 2010 SP1 (если вы используете для установки одну из этих ссылок, следующий софт установится автоматически)
    The Contoso University Приложение, которые вы разработаете, является простым вебсайтом университета.

    Пользователи смогут просматривать и обновлять данные о студентах, курсах и преподавателях. Несколько скриншотов того, что получится, ниже.

    UI близок по стилю к тому, что генерируется шаблонами по умолчанию, поэтому акцент будет на вопросах использования Entity Framework.

    Подходы к разработке с Entity Framework Исхоя из диаграммы, имеется три подхода к работе с данными в Entity Framework: Database First , Model First , и Code First.

    Database First В случае уже имеющейся базы данных Entity Framework может автоматически создать модель данных, состоящую из классов и свойств, соответствующих объектам базы даных (таким, как таблицы и столбцы). Информация о структуре базы (store schema), модель данных (conceptual model ) и маппинг их друг на друга содержится в XML в файле .edmx . Visual Studio предоставляет графический дизайнер Entity Framework, с помощью которого можно просматривать и редактировать .edmx . Части Getting Started With the Entity Framework и Continuing With the Entity Framework в материалах о Web Forms используют подход Database First.Model First Если базы нет, вы можете начать с создания модели данных, используя дизайнер Entity Framework Visual Studio. После окончания работ над моделью дизайнер сгенерирует DDL (data definition language )-код для создания базы. В этом подходе для хранения информации о модели и маппингах также используется .edmx . What"s New in the Entity Framework 4 включает небольшой пример разработки с использованием данного подхода.Code First Вне зависимости от наличия базы вы можете вручную написать код классов и свойств, соответствующих сущностям в базе и использовать этот код с Entity Framework без использования файла . edmx . Именно поэтому можно порой увидеть, как этот подход называют code only , хотя официальное наименование Code First. Маппинг между store schema и conceptual model в represented by your code is handled by convention and by a special mapping API. Если базы ещё нет, Entity Framework может создать, удалить или пересоздать её в случае изменений в модели.

    API доступа к данным, разработанное для Code First, основано на классе DbContext . API может быть использован также и в процессе разработки с подходами Database First и Model First. Для дополнительной информации смотрите When is Code First not code first? В блоге команды разработки Entity Framework.

    POCO (Plain Old CLR Objects) По умолчанию для подходов Database First и Model First классы модели данных наследуются от EntityObject , который и предоставляет функциональность Entity Framework. Это значит, что эти классы не являются persistence ignorant и, таким образом, не полностью соответствуют одном из условий domain-driven design . Все подходы к разработке с Entity Framework также могут работать с POCO (plain old CLR objects ), что, в целом, значит, что они являются persistence-ignorant из-за отсутствия наследования EntityObject .Создание веб-приложения MVC Откройте Visual Studio и создайте новый проект "ContosoUniversity", используя ASP.NET MVC 3 Web Application :

    В New ASP. NET MVC 3 Project выберите шаблон Internet Application и движок представления Razor , снимите галочку с Create a unit test project и нажмите OK .

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

    Для настройки меню Contoso University, в Views\ Shared\_ Layout. cshtml замените текст в h 1 и ссылки в меню, как в примере:

    @ViewBag.Title Contoso University @Html.Partial("_LogOnPartial")

    @RenderBody()
    В Views\Home\Index.cshtml удалите всё в теге h2 .

    В Controllers\HomeController.cs замените "Welcome to ASP.NET MVC!" на "Welcome to Contoso University!"

    В Content\ Site. css для смещения меню влево совершите следующие изменения:

    • В блок #main добавьте clear: both:
    #main { clear: both; padding: 30px 30px 15px 30px; background-color: #fff; border-radius: 4px 0 0 0; -webkit-border-radius: 4px 0 0 0; -moz-border-radius: 4px 0 0 0; }
    • В блоках nav и #menucontainer добавьте clear: both; float: left:
    nav, #menucontainer { margin-top: 40px; clear: both; float: left; }
    Запустите проект.

    Создание модели данных Далее создадим первые классы-сущности для Contoso University. Мы начнём со следующих трёх сущностей:

    Установлена связь один-ко-многим между сущностями Student и Enrollment , и связь один-ко-многим между Course и Enrollment . Другими словами, студент может посещать любое количество курсов, и курс может иметь любое количество студентов, посещающих его.

    В дальнейшем вы создадите классы для каждой из этих сущностей.

    Note: компиляция проекта без созданных классов для этих сущностей вызовет ошибки компиляторов.

    Сущность Student

    В папке Models создайте Student. cs и замените сгенерированный код на:

    Using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Student { public int StudentID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual ICollection
    Свойство StudentID будет первичным ключом соответствующей таблицы. По умолчанию, Entity Framework воспринимает свойство с ID или classname ID как первичный ключ.

    Свойство Enrollments - navigation property . Navigation properties содержат другие сущности, относящиеся к текущей. В данном случае свойство Enrollments содержит в себе все сущности Enrollment , ассоциированные с текущей сущностью Student . Другими словами, если некая запись Student в базе данных имеет связь с двумя записями Enrollment (записей, содержащих значения первичных ключей для студента в поле внешнего ключа StudentID), свойство этой записи Enrollments будет содержать две сущности Enrollment .

    Navigation properties обычно помечаются модификатором virtual дабы использовать возможность Entity Framework, называемую . (суть Lazy loading будет объяснена позже, в Reading Related Data) Если navigation property может содержать несколько сущностей (в связях многие-ко-многим и один-ко-многим), его тип должен быть ICollection .

    Сущность Enrollment

    В папке Models создайте Enrollment. cs со следующим содержанием:

    Using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Enrollment { public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } public decimal? Grade { get; set; } public virtual Course Course { get; set; } public virtual Student Student { get; set; } } }
    Знак вопроса после указания типа decimal указывает на то, что свойство Grade nullable. Оценка, поставленная в null отличная от нулевой оценки- null обозначает то, что оценка еще не выставлена, тогда как 0 – уже значение.

    Свойство StudentID является внешним ключом, и соответствующее navigation property Student . Сущность Enrollment ассоциирована с одной сущностью Student , поэтому свойство может содержать только одну сущность указанного типа (в отличие Student . Enrollments).

    Свойство CourseID является внешним ключом, и соответствующее navigation property Course . Сущность Enrollment ассоциирована с одной сущностью Course .

    Сущность Course

    В папке Models создайтеCourse. cs со следующим содержанием:

    Using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Course { public int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } public virtual ICollection Enrollments { get; set; } } }
    Свойство Enrollments - navigation property. Сущность Course может ассоциироваться с бесконечным множеством сущностей Enrollment .

    Создание Database Context Главный класс, координирующий функциональность Entity Framework для текущей модели данных называется database context . Данный класс наследуется от System . Data . Entity . DbContext . В коде вы определяете, какие сущности включить в модель данных, и также можете определять поведение самого Entity Framework. В нашем коде этот класс имеет название SchoolContext .

    Создайте папку DAL и в ней новый класс SchoolContext. cs :

    Using System; using System.Collections.Generic; using System.Data.Entity; using ContosoUniversity.Models; using System.Data.Entity.ModelConfiguration.Conventions; namespace ContosoUniversity.Models { public class SchoolContext: DbContext { public DbSet Students { get; set; } public DbSet Enrollments { get; set; } public DbSet Courses { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove(); } } }
    Код создаёт свойство DbSet для каждого множества сущностей. В терминологии Entity Framework множество сущностей (entity set) относится к таблице базы данных, и сущность относится к записи в таблице.

    Содержимое метода OnModelCreating защищает имена таблиц от плюрализации, и, если вы этого не делаете, то получаете такие имена таблиц, как Students , Courses , Enrollments . В ином случае имена таблиц будут Student , Course , Enrollment . Разработчики спорят на тему того, нужно ли плюрализовывать имена таблиц или нет. Мы используем одиночную форму, но важен тот момент, что вы можете выбрать, включать эту строчку в код или нет.

    (Этот класс находится в namespace Models потому, что в некоторых ситуациях подход Code First подразумевает нахождение классов сущностей и контекста в одном и том же namespace.)

    Определение Connection String Вам не нужно определять connection string. Если вы не определили эту строку, Entity Framework автоматически создаст базу данных SQL Server Express. Мы, однако, будем работать с SQL Server Compact, и вам необходимо будет создать строку подключения с указанием на это.

    Откройте Web. config и добавьте новую строку подключения в коллекцию connectionStrings . (Убедитесь, что вы обновляете Web. config в корне проекта, так как есть еще один Web. config в папке Views, который трогать не надо .)


    По умолчанию Entity Framework ищет строку подключения, названную также как object context class. Строка подключения, которую вы добавили, определяет базу данных School.sdf, находящуюся в папке App_data и SQL Server Compact.

    Инициализация базы данных с тестовыми данными Entity Framework может автоматически создать базу данных при запуске приложения. Вы можете указать, что это должно выплоняться при каждом запуске приложения или только тогда, когда модель рассинхронизирована с существующей базой. Вы можете также написать класс с методом, который Entity Framework будет автоматически вызывать перед созданием базы для использования её с тестовыми данными. Мы укажем, что база должна удаляться и пересоздаваться при изменении модели.

    В папке DAL создайте новый класс SchoolInitializer. cs с кодом, с помощью которого база будет создана при необходимости и заполнена тестовыми данными.

    Using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Entity; using ContosoUniversity.Models; namespace ContosoUniversity.DAL { public class SchoolInitializer: DropCreateDatabaseIfModelChanges { protected override void Seed(SchoolContext context) { var students = new List { new Student { FirstMidName = "Carson", LastName = "Alexander", EnrollmentDate = DateTime.Parse("2005-09-01") }, new Student { FirstMidName = "Meredith", LastName = "Alonso", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Arturo", LastName = "Anand", EnrollmentDate = DateTime.Parse("2003-09-01") }, new Student { FirstMidName = "Gytis", LastName = "Barzdukas", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Yan", LastName = "Li", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Peggy", LastName = "Justice", EnrollmentDate = DateTime.Parse("2001-09-01") }, new Student { FirstMidName = "Laura", LastName = "Norman", EnrollmentDate = DateTime.Parse("2003-09-01") }, new Student { FirstMidName = "Nino", LastName = "Olivetto", EnrollmentDate = DateTime.Parse("2005-09-01") } }; students.ForEach(s => context.Students.Add(s)); context.SaveChanges(); var courses = new List { new Course { Title = "Chemistry", Credits = 3, }, new Course { Title = "Microeconomics", Credits = 3, }, new Course { Title = "Macroeconomics", Credits = 3, }, new Course { Title = "Calculus", Credits = 4, }, new Course { Title = "Trigonometry", Credits = 4, }, new Course { Title = "Composition", Credits = 3, }, new Course { Title = "Literature", Credits = 4, } }; courses.ForEach(s => context.Courses.Add(s)); context.SaveChanges(); var enrollments = new List { new Enrollment { StudentID = 1, CourseID = 1, Grade = 1 }, new Enrollment { StudentID = 1, CourseID = 2, Grade = 3 }, new Enrollment { StudentID = 1, CourseID = 3, Grade = 1 }, new Enrollment { StudentID = 2, CourseID = 4, Grade = 2 }, new Enrollment { StudentID = 2, CourseID = 5, Grade = 4 }, new Enrollment { StudentID = 2, CourseID = 6, Grade = 4 }, new Enrollment { StudentID = 3, CourseID = 1 }, new Enrollment { StudentID = 4, CourseID = 1, }, new Enrollment { StudentID = 4, CourseID = 2, Grade = 4 }, new Enrollment { StudentID = 5, CourseID = 3, Grade = 3 }, new Enrollment { StudentID = 6, CourseID = 4 }, new Enrollment { StudentID = 7, CourseID = 5, Grade = 2 }, }; enrollments.ForEach(s => context.Enrollments.Add(s)); context.SaveChanges(); } } }
    Метод Seed принимает объект контекста базы как входящий параметр и использует его для добавления новых сущностей в базу. Для каждого типа сущности код создает коллекцию новых сущностей, добавляя их в соответствующее свойство DbSet, и потом сохраняет изменения в базу. Нет необходимости в вызове SaveChanges после каждой группы сущностей, как сделано у нас, но это помогает определить проблему в случае возникновения исключений.

    Измените Global. asax. cs для того, чтобы наш код вызывался при каждом запуске приложения:

    • Добавьте using:
    using System.Data.Entity; using ContosoUniversity.Models; using ContosoUniversity.DAL;
    • В методе Application_Start вызовите метод Entity Framework, который запускает код инициализации базы:
    Database.SetInitializer(new SchoolInitializer());
    Приложение настроено таким образом, что при каждом первом обращении к базе данных после запуска приложения Entity Framework сравнивает базу с моделью (класс SchoolContext), и в случае рассинхронизации приложение удаляет и пересоздает базу.

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

    Далее вы создадите веб-страницу для отображения данных, и процесс запроса данных автоматически инициирует создание базы. Вы начнете с нового контроллера, но перед этим, соберите проект для того, чтобы модель и контекстные классы стали доступны для MVC controller scaffolding.

    Создание контроллера Student Для создание контроллера Student , щелкните на папке Controllers в Solution Explorer , нажмите Add , Controller . В Add Controller совершите следующие действия и изменения и нажмите Add :
    • Controller name: StudentController .
    • Template: Controller with read/write actions and views, using Entity Framework . (по умолчанию.)
    • Model class: Student (ContosoUniversity.Models) . (если этого нет, пересоберите проект)
    • Data context class: SchoolContext (ContosoUniversity.Models) .
    • Views: Razor (CSHTML) . (по умолчанию)

    Откройте Controllers\ StudentController. cs , вы увидите созданную переменную, инициализирующую объект контекста базы данных:

    Private SchoolContext db = new SchoolContext();
    Действие Index собирает список студентов из свойства Students из экземпляра контекста базы данных:

    Public ViewResult Index() { return View(db.Students.ToList()); }
    Автоматическое scaffolding было создано для множества Student . Для настройки заголовков и последовательности колонок откройте Views\ Student\ Index. cshtml и замените код на:

    @model IEnumerable @{ ViewBag.Title = "Students"; } Students

    @Html.ActionLink("Create New", "Create")

    Last Name First Name Enrollment Date @foreach (var item in Model) { }
    @Html.ActionLink("Edit", "Edit", new { id=item.StudentID }) | @Html.ActionLink("Details", "Details", new { id=item.StudentID }) | @Html.ActionLink("Delete", "Delete", new { id=item.StudentID }) @Html.DisplayFor(modelItem => item.LastName) @Html.DisplayFor(modelItem => item.FirstMidName) @Html.DisplayFor(modelItem => item.EnrollmentDate)

    Запустите сайт, нажмите на вкладку Students .

    Закройте браузер. В Solution Explorer выберите проект ContosoUniversity . Нажмите Show all Files , Refresh и затем разверните папку App_Data .

    Два раза щелкните на School. sdf для открытия Server Explorer , и Tables .

    Note если у вас возникает ошибка после того, как вы два раза щелкаете на School.sdf, убедитесь, что вы установили Visual Studio 2010 SP1 Tools for SQL Server Compact 4.0 . Если все установлено, перезапустите Visual Studio.

    Для каждой таблицы есть свое множество сущностей + одна дополнительная таблица. EdmMetadata используется для определения Entity Framework, когда случилась разсинхронизация модели и базы.

    Щелкните на одной из таблиц и Show Table Data чтобы увидеть загруженные классом SchoolInitializer данные.

    Закройте подключение, иначе может возникнуть проблема с запуском приложения.

    Соглашения Количество кода, нужное для создания Entity Framework базы, минимально из-за использования (conventions) Entity Framework. Некоторые из них уже были упомянуты:
    • Форма множественного числа имен классов сущностей используется в качестве имен таблиц.
    • Имена свойств сущностей используется в качестве имен столбцов.
    • Свойства сущностей с именами ID или classname ID распознаются как первичные ключи.
    • Entity Framework подключается к базе, отыскав строку подключения с таким же именем, как и класс контекста (в данном случае SchoolContext).
    Вы видели, что данные соглашения могут быть перекрыты (допустим, плюрализацию можно отключить) и вы можете узнать больше о том, как это делать, из

    4 ответов

    В Entity Framework Core 2.1 мы можем использовать типы запросов, как предполагал Юрий Н.

    Более подробную статью о том, как их использовать, можно найти

    Самый простой подход в соответствии с примерами статей будет:

    1. Мы имеем, например, следующие сущности Модели для управления публикациями

    Public class Magazine { public int MagazineId { get; set; } public string Name { get; set; } public string Publisher { get; set; } public List Articles { get; set; } } public class Article { public int ArticleId { get; set; } public string Title { get; set; } public int MagazineId { get; set; } public DateTime PublishDate { get; set; } public Author Author { get; set; } public int AuthorId { get; set; } } public class Author { public int AuthorId { get; set; } public string Name { get; set; } public List Articles { get; set; } }

    2. У нас есть вид под названием AuthorArticleCounts, определенный для возврата имени и количества статей, написанных автором

    SELECT a.AuthorName, Count(r.ArticleId) as ArticleCount from Authors a JOIN Articles r on r.AuthorId = a.AuthorId GROUP BY a.AuthorName

    3. Мы идем и создаем модель, которая будет использоваться для представления

    Public class AuthorArticleCount { public string AuthorName { get; private set; } public int ArticleCount { get; private set; } }

    4. После этого создадим свойство DbQuery в моем DbContext, чтобы использовать результаты просмотра внутри модели

    Public DbQuery AuthorArticleCounts{get;set;}

    5. Наконец, мы можем легко получить результаты представления как это.

    Var results=_context.AuthorArticleCounts.ToList();

    В настоящее время представления не поддерживаются Entity Framework Core. См. https://github.com/aspnet/EntityFramework/issues/827 .

    Таким образом, вы можете обмануть EF, используя представление, сопоставляя свою сущность с представлением, как если бы это была таблица. Этот подход связан с ограничениями. например вы не можете использовать миграцию, вам нужно вручную указать ключ для EF для нас, и некоторые запросы могут работать некорректно. Чтобы обойти эту последнюю часть, вы можете писать SQL-запросы вручную

    Context.Images.FromSql("SELECT * FROM dbo.ImageView")

    EF Core не создает DBset для представлений SQL автоматически в контексте calss, мы можем добавить их вручную, как показано ниже.

    Public partial class LocalDBContext: DbContext { public LocalDBContext(DbContextOptions options) : base(options) { } public virtual DbSet YourView { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { entity.HasKey(e => e.ID); entity.ToTable("YourView"); entity.Property(e => e.Name).HasMaxLength(50); }); } }

    Образец образца определяется ниже, с несколькими свойствами

    Предположим достаточно типовой сценарий — добавление множества объектов в базу:

    Бездумное выключение свойства AutoDetectChangesEnabled может привести к нежелательным последствиям (потеря изменений, исключения из-за нарушения целостности данных), поэтому наиболее простое правило я бы сформулировал так — если ваш код не предполагает дальнейшего изменения добавленных в контекст объектов в пределах той же сессии, то это свойство можно смело отключать. Такая ситуация встречается довольно часто — типовой CRUD API обычно получает объект извне и либо просто его добавляет, либо еще определяет, какие были сделаны изменения с момента вычитки, и соответствующим образом обновляет информацию о состоянии объекта в контексте (например, с помощью GraphDiff , или с использованием self-tracked entities, или любых других похожих решений). Сам объект при этом не изменяется.

    Постоянная перекомпиляция некоторых запросов Начиная с Entity Framework 5, запросы автоматически кешируются после компиляции, что позволяет значительно ускорить их последующие выполнения — текст SQL запроса будет взят из кеша, остается только подставить требуемые значения параметров. Но есть несколько ситуаций, в которых компиляция будет выполняться при каждом выполнении.Использование Contains по коллекции в памяти На практике нередко возникает необходимость добавить в запрос условие, аналогичное SQL-оператору IN — проверить, совпадает ли значение свойства с каким-нибудь из элементов коллекции. Например, вот так:

    List channels = new List { 1, 5, 9 }; dataContext.Entities .AsNoTracking() .Where(e => channels.Contains(e.Channel)) .ToList();
    Это выражение в итоге преобразуется в SQL следующего вида:

    SELECT . AS , . AS , . AS FROM . AS WHERE . IN (1, 5, 9)
    Получается, что для оператора IN параметры не используются, а вместо этого подставляются сами значения. Такой запрос закешировать не получится, т.к. при использовании коллекции с другим содержимым текст запроса нужно будет перегенерировать. Это, кстати, бьет не только по производительности самого Entity Framework, но и по серверу базы данных, так как для любого нового списка значений в операторе IN сервер должен будет заново построить и закешировать план выполнения.

    Если в коллекции, по которой делается Contains не ожидается большого числа элементов (скажем, не больше ста), проблему можно решить динамической генерацией условий, соединенных оператором OR. Это легко сделать, например, с помощью библиотеки LinqKit :

    List channels = new List { 1, 5, 9 }; var channelsCondition = PredicateBuilder.False(); channelsCondition = channels.Aggregate(channelsCondition, (current, value) => current.Or(e => e.Channel == value).Expand()); var query = dataContext.Entities .AsNoTracking() .Where(channelsCondition);
    В итоге получаем уже параметризированный запрос:

    SELECT . AS , . AS , . AS FROM . AS WHERE . IN (@p__linq__0,@p__linq__1,@p__linq__2)
    Несмотря на то, что динамическое построение запроса выглядит дополнительной затратной работой, на практике на него уходит сравнительно немного процессорного времени. В одной из реальных задач построение запроса при каждом вызове занимало больше секунды. А замена Contains на подобное динамическое выражение уменьшило время обработки запросов (кроме первого) до десятков миллисекунд.

    Использование Take и Skip Во многих проектах возникает необходимость реализовать пейджинг для результатов поиска. Очевидным решением для выборки нужной порции записей тут являются функции Take и Skip:

    Int pageSize = 10; int startFrom = 10; var query = dataContext.Entities .AsNoTracking() .OrderBy(e => e.Name) .Skip(startFrom) .Take(pageSize);
    Посмотрим, какой в этом случае будет SQL:

    SELECT . AS , . AS , . AS FROM . AS ORDER BY . ASC OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY
    И размер страницы, и величина смещения указаны в запросе константами, а не параметрами. Это, опять же, говорит о том, что текст запроса кешироваться не будет. К счастью, начиная с Entity Framework 6 есть простая возможность обойти эту проблему — использовать лямбда-выражения в функциях Take и Skip:

    Var query = dataContext.Entities .AsNoTracking() .OrderBy(e => e.Name) .Skip(() => startFrom) .Take(() => pageSize);
    И результирующий запрос будет содержать параметры вместо констант:

    SELECT . AS , . AS , . AS FROM . AS ORDER BY . ASC OFFSET @p__linq__0 ROWS FETCH NEXT @p__linq__1 ROWS ONLY

    Большое количество Include в одном запросе Очевидно, самый простой способ прочитать данные из базы вместе с дочерними коллекциями и другими навигационными свойствами — это использовать метод Include(). Независимо от количества Include() в LINQ запросе, по итогу будет сформирован один SQL запрос, который возвращает все указанные данные. Может сложиться впечатление, что в рамках Entity Framework такой подход для вычитки сложных объектов будет наиболее оптимальным в любой ситуации. Но это не совсем так.

    Для начала рассмотрим структуру итогового SQL запроса. Например, у нас есть LINQ запрос с двумя Include для коллекций.

    Var query = c.GuidKeySequentialParentEntities .AsNoTracking() .Include(e => e.Children1) .Include(e => e.Children2) .Where(e => e.Id == sequentialGuidKey);
    Соответствующий SQL будет содержать UNION ALL:

    SELECT . AS , . AS , . AS , . AS , . AS , . AS , . AS , . AS , . AS , . AS FROM (SELECT CASE WHEN (. IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS , 1 AS , . AS , . AS , . AS , . AS , . AS , CAST(NULL AS uniqueidentifier) AS , CAST(NULL AS varchar(1)) AS , CAST(NULL AS uniqueidentifier) AS FROM . AS LEFT OUTER JOIN . AS ON . = . WHERE . = @p__linq__0 UNION ALL SELECT 2 AS , 2 AS , . AS , . AS , CAST(NULL AS uniqueidentifier) AS , CAST(NULL AS varchar(1)) AS , CAST(NULL AS uniqueidentifier) AS , . AS , . AS , . AS FROM . AS INNER JOIN . AS ON . = . WHERE . = @p__linq__0) AS ORDER BY . ASC, . ASC
    Логично было бы предположить, что Include() просто добавляет еще один JOIN в запрос. Но Entity Framework ведет себя сложнее. Если включаемое навигационное свойство — единичный объект, а не коллекция, то будет просто еще один JOIN. Если коллекция — то под каждую будет сформирован отдельный подзапрос, где родительская таблица соединяется с дочерней, а все такие подзапросы будут объединены в общий UNION ALL. Очевидно, что если нужна только одна дочерняя коллекция, то UNION ALL не будет. Схематически это можно изобразить так:

    SELECT /* список полей */ FROM (SELECT /* список полей */ FROM /* родительская таблица */ LEFT OUTER JOIN /* дочерняя таблица 1 */ WHERE /* общее условие */ UNION ALL SELECT /* список полей */ FROM /* родительская таблица */ INNER JOIN /* дочерняя таблица 2 */ WHERE /* общее условие */ UNION ALL SELECT /* список полей */ FROM /* родительская таблица */ INNER JOIN /* дочерняя таблица 3 */ WHERE /* общее условие */ /* ... */ ORDER BY /* список полей */
    Сделано это для борьбы с проблемой перемножения результатов. Предположим, у объекта есть три дочерних коллекции по 10 элементов в каждой. Если все три добавить через OUTER JOIN напрямую в «главный» запрос, то в результате будет 10 * 10 * 10 = 1000 записей. Если же пойти путем Entity Framework, и эти три коллекции собирать в один запрос через UNION, то получим 30 записей. Чем больше коллекций и элементов в них, тем выигрыш подхода с UNION очевиднее.

    Но проблема в том, что при большой сложности самих сущностей и критериев выборки, построение и оптимизация такого запроса весьма трудоемки для Entity Framework, как и выполнение его на уровне сервера базы данных. Поэтому если результаты профилирования показывают неудовлетворительную производительность запросов, содержащих Include, а с индексами в базе все в порядке — есть смысл задуматься об альтернативных решениях.

    Основная идея альтернативных решений — это вычитка каждой коллекции отдельным запросом. Наиболее простой вариант возможен, если объекты при выборке добавляются в контекст, т.е. без использования AsNoTracking():

    Var children1 = c.ChildEntities1 .Where(e => e.Parent.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) var children2 = c.ChildEntities2 .Where(e => e.Parent.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) children1.Load(); children2.Load(); var query = c.ParentEntities .Where(e => e.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .ToList();
    Получается, что для каждой дочерней коллекции мы вычитываем все объекты, которые имеют отношение к родительским сущностям, попадающим под критерий запроса. После вызова Load() объекты добавляются в контекст. Во время вычитки родительских сущностей Entity Framework найдет все дочерние, находящиеся в контексте, и соответствующим образом добавит на них ссылки.

    Основной недостаток здесь — то, что на каждый запрос идет отдельное обращение к серверу базы данных. К счастью, есть способ решить и эту проблему. В библиотеке EntityFramework.Extended есть возможность создавать «будущие» запросы. Основная идея в том, что все запросы, у которых был вызван extension method Future(), будут посланы в одном обращении к серверу, когда у какого-либо из них будет вызван терминальный метод:

    Var children1 = c.ChildEntities1 .Where(e => e.Parent.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .Future(); var children2 = c.ChildEntities2 .Where(e => e.Parent.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .Future(); var results = c.ParentEntities .Where(e => e.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .Future() .ToList();
    По итогу, как и в первом примере, объекты из коллекции results будут содержать корректно заполненные коллекции Children1 и Children2, причем все данные будут получены за одно обращение к серверу.

    Использование «будущих» запросов будет полезно в любых ситуациях, где есть необходимость выполнять несколько отдельных запросов.

    Вычитка полей только из базовой сущности при использовании Table Per Type маппинга Представим себе систему, в которой ряд сущностей имеет базовый класс, содержащий их общие характеристики (название, дата создания, владелец, статус и т.д.). Также есть требование реализовать поиск по этим характеристикам и отображение списка результатов. Отображение подразумевает, опять же, использование только базовых характеристик.

    С точки зрения гибкости модели под эту задачу хорошо подходит Table Per Type маппинг, где под каждый тип создается отдельная таблица. Например, у нас есть базовый класс Vehicle и наследники — PassengerCar, Truck, Motorcycle. В этом случае в базе будет создано четыре таблицы.

    Напишем запрос, который вычитывает результаты поиска по какому-либо критерию. Например, дата добавления не ранее 10 дней назад:

    Var vehicles = context.Vehicles .AsNoTracking() .Where(v => v.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -10)) .ToList();
    И посмотрим, во что его преобразует Entity Framework:

    SELECT CASE WHEN ((NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL)))) THEN "0X" WHEN ((. = 1) AND (. IS NOT NULL)) THEN "0X0X" WHEN ((. = 1) AND (. IS NOT NULL)) THEN "0X1X" ELSE "0X2X" END AS , . AS , . AS , . AS , CASE WHEN ((NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL)))) THEN CAST(NULL AS bit) WHEN ((. = 1) AND (. IS NOT NULL)) THEN . WHEN ((. = 1) AND (. IS NOT NULL)) THEN CAST(NULL AS bit) END AS , CASE WHEN ((NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL)))) THEN CAST(NULL AS int) WHEN ((. = 1) AND (. IS NOT NULL)) THEN CAST(NULL AS int) WHEN ((. = 1) AND (. IS NOT NULL)) THEN . END AS , CASE WHEN ((NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL)))) THEN CAST(NULL AS int) WHEN ((. = 1) AND (. IS NOT NULL)) THEN CAST(NULL AS int) WHEN ((. = 1) AND (. IS NOT NULL)) THEN CAST(NULL AS int) ELSE . END AS FROM . AS LEFT OUTER JOIN (SELECT . AS , . AS , cast(1 as bit) AS FROM . AS ) AS ON . = . LEFT OUTER JOIN (SELECT . AS , . AS , cast(1 as bit) AS FROM . AS ) AS ON . = . LEFT OUTER JOIN (SELECT . AS , . AS , cast(1 as bit) AS FROM . AS ) AS ON . = . WHERE . >
    Получается, что нам нужна только базовая информация, а Entity Framework вычитывает всю, причем достаточно громоздким запросом. На самом деле в данной конкретной ситуации ничего плохого нет — несмотря на то, что мы выбираем объекты из коллекции базовых классов, фреймворк должен соблюдать полиморфное поведение и возвращать объект того типа, которым он был создан.

    Основной вопрос здесь — как упростить запрос, чтобы он не читал лишнее? К счастью, начиная с Entity Framework 5 такая возможность есть — это использование проекции. Просто создаем объект другого типа или анонимный, используя для его заполнения только свойств базовой сущности:

    Var vehicles = context.Vehicles .AsNoTracking() .Where(v => v.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -10)) .Select(v => new { Id = v.Id, CreatedAt = v.CreatedAt, Name = v.Name }) .ToList();
    И все становится намного проще:

    SELECT 1 AS , . AS , . AS , . AS FROM . AS WHERE . >= (DATEADD (day, -10, SysUtcDateTime()))
    Но есть и неприятные новости – если в базовом классе есть коллекция, и ее нужно вычитывать, проблема остается. Вот пример:

    Var vehicles = context.Vehicles .AsNoTracking() .Where(v => v.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -10)) .Select(v => new { Id = v.Id, CreatedAt = v.CreatedAt, Name = v.Name, ServiceTickets = v.ServiceTickets }) .ToList();
    И сгенерированный для него SQL:

    SELECT . AS , . AS , . AS , . AS , . AS , . AS , . AS , . AS , . AS , . AS , . AS FROM (SELECT . AS , . AS , . AS , . AS , . AS , . AS , 1 AS , . AS , . AS , . AS , CASE WHEN (. IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS FROM . AS LEFT OUTER JOIN . AS ON . = . LEFT OUTER JOIN . AS ON . = . LEFT OUTER JOIN . AS ON . = . LEFT OUTER JOIN . AS ON . = . WHERE . >= (DATEADD (day, -10, SysUtcDateTime()))) AS ORDER BY . ASC, . ASC, . ASC, . ASC, . ASC
    Я создавал тикет для Entity Framework на эту тему: https://entityframework.codeplex.com/workitem/2814 , но мне вежливо ответили, что в виду большой сложности и опасности все разломать, они это исправлять не будут.

    В некоторых случаях, когда размер базы и/или количество объектов-наследников невелики, с этим можно жить. Если подобные запросы начинают ощутимо ухудшать производительность, нужно искать решения. Раз на уровне самого фреймворка проблему предотвратить нельзя, нужен обходной путь. Наиболее простой вариант здесь — дочитывать коллекции отдельными запросами, например:

    //Создаем базовый запрос var vehiclesQuery = context.Vehicles .AsNoTracking() .Where(v => v.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -10)); //Вычитываем объекты с помощью проекции на вспомогательный класс, игнорируя коллекции var vehicles = vehiclesQuery .Select(v => new VehicleDto { Id = v.Id, CreatedAt = v.CreatedAt, Name = v.Name }) .ToList(); //Дочитываем элементы коллекции, относящиеся к любому из объектов, возвращаемых исходным запросом var serviceTickets = context.ServiceTickets .AsNoTracking() .Where(s => vehiclesQuery.Any(v => v.Id == s.VehicleId)) .ToList(); //Раскладываем элементы по соответствующим объектам vehicles.ForEach(v => v.ServiceTickets .AddRange(serviceTickets.Where(s => s.VehicleId == v.Id)));
    Универсального рецепта здесь нет, и приведенное выше решение может не дать выигрыша во всех случаях. Например, базовый запрос может оказаться достаточно сложным, и выполнять его по новой для каждой коллекции будет накладно. Попытаться обойти эту проблему можно через получение списка идентификаторов из результатов базового запроса, а потом использование его во всех дальнейших подзапросах. Но если результатов много, выигрыша может и не быть. К тому же, в этом случае следует помнить о том, что было сказано ранее о методе Contains, который явно напрашивается для поиска по идентификаторам.

    Общий подход к решению проблемы я бы сформулировал так — если есть возможность не использовать Table Per Type маппинг, лучше его не использовать. В тех случаях, когда без него сложно обойтись, нужно попробовать варианты, описанные выше, и посмотреть, дают ли они выигрыш.

    Дополнительная информация Нюансы, связанные с производительностью, на которые следует обратить внимание при работе с Entity Framework (в том числе и описанные в статье) кратко описаны по этой ссылке: https://msdn.microsoft.com/en-us/data/hh949853.aspx . К сожалению, не для всех проблем указаны альтернативные решения, но информация все равно очень полезная. Также следует отметить, что как минимум пункт 4.3 на практике не подтверждается для Entity Framework 6.1.3.

    Вы можете помочь и перевести немного средств на развитие сайта

     
    работы core

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

    Это сообщение сообщества, поэтому, пожалуйста, добавьте его, как вы считаете нужным.

    Применяется : Entity Framework 1.0 поставляется с Visual Studio 2008 sp1.

    Зачем выбирать EF в первую очередь?

    Учитывая, что это молодая технология с множеством проблем (см. Ниже), это может быть трудная продажа, чтобы попасть на победителя EF для вашего проекта. Тем не менее, это технология Microsoft толкает (за счет Linq2Sql, который является подмножеством EF). Кроме того, вы не можете быть удовлетворены NHibernate или другими решениями там. Какими бы ни были причины, есть люди (включая меня), работающие с EF, и жизнь не плохая. Подумайте.

    EF и наследование

    Первым крупным предметом является наследование. EF поддерживает сопоставление для унаследованных классов, которые сохраняются двумя способами: таблица для каждого класса и таблица иерархии. Моделирование простое, и с этой частью проблем с программированием нет.

    (Ниже приведена таблица для каждой модели класса, так как у меня нет опыта работы с таблицей на иерархию, которая в любом случае ограничена). Реальная проблема возникает, когда вы пытаетесь запустить запросы, включающие один или несколько объектов, которые являются частью дерево наследования: сгенерированный sql невероятно ужасен, требуется много времени, чтобы получить синтаксический анализ EF и требуется много времени для выполнения. Это настоящий шоу-стоппер. Достаточно того, что EF, вероятно, не следует использовать с наследованием или как можно меньше.

    Вот пример того, насколько это было плохо. Моя модель EF имела ~ 30 классов, ~ 10 из которых были частью дерева наследования. При запуске запроса для получения одного элемента из базового класса, такого простого, как Base.Get (id), сгенерированный SQL был более 50 000 символов. Затем, когда вы пытаетесь вернуть некоторые ассоциации, он еще больше вырождается, и он бросает исключения SQL из-за невозможности запросить более 256 таблиц сразу.

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

    Итак, рекомендации? Избегайте наследования, если можете, производительность будет намного лучше. Используйте его экономно там, где вам нужно. На мой взгляд, это делает EF прославленным инструментом генерации sql-запросов для запросов, но все же есть преимущества для его использования. И способы реализовать механизм, похожий на наследование.

    Обход наследования с интерфейсами

    Первое, что нужно знать, пытаясь получить какое-то наследство, идущее с EF, - это то, что вы не можете назначить класс, не относящийся к EF, базовому классу. Даже не пытайтесь, он будет перезаписан модератором. Так что делать?

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

    Public enum EntityTypes{ Unknown = -1, Dog = 0, Cat } public interface IEntity { int EntityID { get; } string Name { get; } Type EntityType { get; } } public partial class Dog: IEntity { // implement EntityID and Name which could actually be fields // from your EF model Type EntityType{ get{ return EntityTypes.Dog; } } }

    Используя этот IEntity, вы можете работать с неопределенными ассоциациями в других классах

    // lets take a class that you defined in your model. // that class has a mapping to the columns: PetID, PetType public partial class Person { public IEntity GetPet() { return IEntityController.Get(PetID,PetType); } }

    который использует некоторые функции расширения:

    Public class IEntityController { static public IEntity Get(int id, EntityTypes type) { switch (type) { case EntityTypes.Dog: return Dog.Get(id); case EntityTypes.Cat: return Cat.Get(id); default: throw new Exception("Invalid EntityType"); } } }

    Не так аккуратно, как наличие простого наследования, особенно учитывая, что вы должны хранить PetType в дополнительном поле базы данных, но, учитывая прирост производительности, я бы не стал оглядываться назад.

    Он также не может моделировать отношения «один ко многим», «многие ко многим», но с творческим использованием «Союза» его можно заставить работать. Наконец, он создает побочный эффект загрузки данных в свойство / функцию объекта, о котором вы должны быть осторожны. Использование четкого соглашения об именах, такого как GetXYZ (), помогает в этом отношении.

    Скомпилированные запросы

    Производительность Entity Framework не так хороша, как прямой доступ к базе данных с ADO (очевидно) или Linq2SQL. Однако есть способы улучшить его, один из которых заключается в компиляции ваших запросов. Производительность скомпилированного запроса похожа на Linq2Sql.

    Что такое скомпилированный запрос? Это просто запрос, для которого вы указываете инфраструктуре, чтобы сохранить синтаксическое дерево в памяти, поэтому его не нужно восстанавливать при следующем запуске. Итак, в следующем прогоне вы сэкономите время, затрачиваемое на разбор дерева. Не обесценивайте это, поскольку это очень дорогостоящая операция, которая становится еще хуже с более сложными запросами.

    Существует два способа скомпилировать запрос: создание ObjectQuery с EntitySQL и использование функции CompiledQuery.Compile (). (Обратите внимание, что с помощью EntityDataSource на вашей странице вы фактически будете использовать ObjectQuery с EntitySQL, чтобы скомпилировать и кэшировать).

    Отбросьте здесь, если вы не знаете, что такое EntitySQL. Это строковый способ написания запросов к EF. Вот пример: «выберите значение dog из Entities.DogSet как собака, где dog.ID = @ID». Синтаксис довольно похож на синтаксис SQL. Вы также можете сделать довольно сложную манипуляцию с объектом, что хорошо объяснено [здесь] .

    Итак, вот как это сделать, используя ObjectQuery

    oQuery = new ObjectQuery(query, EntityContext.Instance)); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault();

    При первом запуске этого запроса структура создаст дерево выражений и сохранит его в памяти. Поэтому в следующий раз, когда он будет выполнен, вы сэкономите на этом дорогостоящем шаге. В этом примере EnablePlanCaching = true, что необязательно, поскольку это опция по умолчанию.

    Другим способом компиляции запроса для последующего использования является метод CompiledQuery.Compile. Это использует делегат:

    Static readonly Func ((ctx, id) => ctx.DogSet.FirstOrDefault(it => it.ID == id));

    или используя linq

    Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id) => (from dog in ctx.DogSet where dog.ID == id select dog).FirstOrDefault());

    для вызова запроса:

    Query_GetDog.Invoke(YourContext, id);

    Преимущество CompiledQuery заключается в том, что синтаксис вашего запроса проверяется во время компиляции, где не существует EntitySQL. Однако есть и другие соображения...

    Включает

    Допустим, вы хотите, чтобы данные для владельца собаки возвращались по запросу, чтобы избежать внесения 2 вызовов в базу данных. Легко сделать, правильно?

    String query = "select value dog " + "from Entities.DogSet as dog " + "where dog.ID = @ID"; ObjectQuery oQuery = new ObjectQuery(query, EntityContext.Instance)).Include("Owner"); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault();

    Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id) => (from dog in ctx.DogSet.Include("Owner") where dog.ID == id select dog).FirstOrDefault());

    Итак, что, если вы хотите, чтобы параметр Include был параметризован? Я имею в виду, что вы хотите иметь единственную функцию Get (), которая вызывается с разных страниц, которые заботятся о разных отношениях для собаки. Один заботится о Владельце, другой о его Любимом Фуду, другом о его FavotireToy и так далее. В основном вы хотите указать запрос, какие ассоциации загружать.

    Это легко сделать с EntitySQL

    Public Dog Get(int id, string include) { string query = "select value dog " + "from Entities.DogSet as dog " + "where dog.ID = @ID"; ObjectQuery oQuery = new ObjectQuery(query, EntityContext.Instance)) .IncludeMany(include); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault(); }

    В include просто используется переданная строка. Достаточно легко. Обратите внимание, что можно улучшить функцию Include (string) (которая принимает только один путь) с помощью IncludeMany (string), которая позволит вам передать цепочку разделенных запятыми ассоциаций для загрузки. Посмотрите далее в разделе расширения этой функции.

    Однако, если мы попытаемся сделать это с помощью CompiledQuery, мы столкнемся с многочисленными проблемами:

    Очевидное

    Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id, include) => (from dog in ctx.DogSet.Include(include) where dog.ID == id select dog).FirstOrDefault());

    будет подавляться при вызове:

    Query_GetDog.Invoke(YourContext, id, "Owner,FavoriteFood");

    Поскольку, как упоминалось выше, Include () хочет видеть только один путь в строке, и здесь мы даем ему 2: «Owner» и «FavoriteFood» (что не следует путать с «Owner.FavoriteFood»!).

    Затем давайте использовать IncludeMany (), которая является функцией расширения

    Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id, include) => (from dog in ctx.DogSet.IncludeMany(include) where dog.ID == id select dog).FirstOrDefault());

    Неправильно снова, на этот раз это потому, что EF не может разобрать IncludeMany, потому что он не является частью функций, которые распознаются: это расширение.

    Итак, вы хотите передать произвольное количество путей к вашей функции, а Includes () - только один. Что делать? Вы можете решить, что вам никогда не понадобится больше, чем, скажем, 20 включений, и передать каждую разделенную строку в struct в CompiledQuery. Но теперь запрос выглядит так:

    From dog in ctx.DogSet.Include(include1).Include(include2).Include(include3) .Include(include4).Include(include5).Include(include6) .[...].Include(include19).Include(include20) where dog.ID == id select dog

    что тоже ужасно. Хорошо, тогда подождите, подождите. Не можем ли мы вернуть ObjectQuery с помощью CompiledQuery? Затем установите для этого включения? Ну, что бы я подумал так:

    Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id) => (ObjectQuery)(from dog in ctx.DogSet where dog.ID == id select dog)); public Dog GetDog(int id, string include) { ObjectQuery oQuery = query_GetDog(id); oQuery = oQuery.IncludeMany(include); return oQuery.FirstOrDefault; }

    Это должно сработать, за исключением того, что когда вы вызываете IncludeMany (или Include, Where, OrderBy ...), вы аннулируете кешированный скомпилированный запрос, потому что теперь он совершенно новый! Итак, дерево выражений должно быть переписано, и вы снова получите эту производительность.

    Так в чем же решение? Вы просто не можете использовать CompiledQueries с параметризованным Includes. Вместо этого используйте EntitySQL. Это не означает, что для CompiledQueries не используются. Это отлично подходит для локализованных запросов, которые всегда будут вызываться в одном контексте. В идеале CompiledQuery всегда следует использовать, потому что синтаксис проверяется во время компиляции, но из-за ограничений это невозможно.

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

    Передача более 3 параметров в CompiledQuery

    Func ограничивается 5 параметрами, последним из которых является тип возврата, а первый - ваш объект Entities из модели. Таким образом, вы получите 3 параметра. Сила, но ее можно улучшить очень легко.

    Public struct MyParams { public string param1; public int param2; public DateTime param3; } static readonly Func query_GetDog = CompiledQuery.Compile((ctx, myParams) => from dog in ctx.DogSet where dog.Age == myParams.param2 && dog.Name == myParams.param1 and dog.BirthDate > myParams.param3 select dog); public List GetSomeDogs(int age, string Name, DateTime birthDate) { MyParams myParams = new MyParams(); myParams.param1 = name; myParams.param2 = age; myParams.param3 = birthDate; return query_GetDog(YourContext,myParams).ToList(); }

    Типы возврата (это не относится к запросам EntitySQL, поскольку они не скомпилированы в то же время во время выполнения в качестве метода CompiledQuery)

    Работая с Linq, вы обычно не принудительно выполняете выполнение запроса до самого последнего момента, если некоторые другие функции в нисходящем направлении хотят каким-то образом изменить запрос:

    Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, age, name) => from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog); public IEnumerable GetSomeDogs(int age, string name) { return query_GetDog(YourContext,age,name); } public void DataBindStuff() { IEnumerable dogs = GetSomeDogs(4,"Bud"); // but I want the dogs ordered by BirthDate gridView.DataSource = dogs.OrderBy(it => it.BirthDate); }

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

    Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, age, name) => from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog); public List GetSomeDogs(int age, string name) { return query_GetDog(YourContext,age,name).ToList(); // it.BirthDate); }

    Когда вы вызываете ToList (), запрос выполняется в соответствии с скомпилированным запросом, а затем, позже, OrderBy выполняется против объектов в памяти. Это может быть немного медленнее, но я даже не уверен. Убедительная вещь в том, что вы не беспокоитесь о неправильной обработке ObjectQuery и недействительности скомпилированного плана запросов.

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

    Представление

    Каково влияние компиляции запроса на производительность? Это может быть довольно большой. Эмпирическое правило заключается в том, что компиляция и кэширование запроса для повторного использования занимает как минимум удвоенное время простое выполнение без кеширования. Для сложных запросов (read inherirante) я видел вверх до 10 секунд.

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

    Когда вы загружаете страницу с предварительно скомпилированными запросами, вы в первый раз получаете хит. Он загрузится, возможно, через 5-15 секунд (очевидно, будет вызвано больше, чем один предварительно скомпилированный запрос), а последующие нагрузки занимают менее 300 мс. Драматическая разница, и вам решать, нормально ли для вашего первого пользователя сделать хит или вы хотите, чтобы скрипт вызывал ваши страницы, чтобы заставить компиляцию запросов.

    Можно ли кэшировать этот запрос?

    { Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog; }

    Нет, специальные запросы Linq не кэшируются, и вы понесете затраты на создание дерева каждый раз, когда вы его вызываете.

    Параметризированные запросы

    Большинство возможностей поиска связаны с сильно параметризованными запросами. Существуют даже доступные библиотеки, которые позволят вам построить параметризованный запрос из выражений lamba. Проблема в том, что вы не можете использовать предварительно скомпилированные запросы с ними. Один из способов заключается в том, чтобы отобразить все возможные критерии запроса и флага, которые вы хотите использовать:

    Public struct MyParams { public string name; public bool checkName; public int age; public bool checkAge; } static readonly Func query_GetDog = CompiledQuery.Compile((ctx, myParams) => from dog in ctx.DogSet where (myParams.checkAge == true && dog.Age == myParams.age) && (myParams.checkName == true && dog.Name == myParams.name) select dog); protected List GetSomeDogs() { MyParams myParams = new MyParams(); myParams.name = "Bud"; myParams.checkName = true; myParams.age = 0; myParams.checkAge = false; return query_GetDog(YourContext,myParams).ToList(); }

    Преимущество здесь в том, что вы получаете все преимущества предварительно скомпилированного quert. Недостатки заключаются в том, что вы, скорее всего, закончите предложение where, которое довольно сложно поддерживать, что вы понесете больший штраф за предварительную компиляцию запроса и что каждый выполненный вами запрос не так эффективен, как может быть (в частности, с вставками).

    Другой способ - построить запрос EntitySQL по частям, как мы все это сделали с SQL.

    Protected List GetSomeDogs(string name, int age) { string query = "select value dog from Entities.DogSet where 1 = 1 "; if(!String.IsNullOrEmpty(name)) query = query + " and dog.Name == @Name "; if(age > 0) query = query + " and dog.Age == @Age "; ObjectQuery oQuery = new ObjectQuery(query, YourContext); if(!String.IsNullOrEmpty(name)) oQuery.Parameters.Add(new ObjectParameter("Name", name)); if(age > 0) oQuery.Parameters.Add(new ObjectParameter("Age", age)); return oQuery.ToList(); }

    Здесь возникают проблемы: - во время компиляции нет проверки синтаксиса - каждая комбинация параметров генерирует другой запрос, который необходимо предварительно скомпилировать при первом запуске. В этом случае существует только 4 разных возможных запроса (без параметров, только по возрасту, только по имени и оба параметра), но вы можете видеть, что с обычным мировым поиском может быть больше. - Никто не любит конкатенировать струны!

    Другой вариант - запросить большое подмножество данных, а затем сузить его в памяти. Это особенно полезно, если вы работаете с определенным подмножеством данных, как и все собаки в городе. Вы знаете, что есть много, но вы также знаете, что их не так много... поэтому ваша страница поиска CityDog может загружать все собаки для города в память, что является одним предварительно скомпилированным запросом, а затем уточняет результаты

    Protected List GetSomeDogs(string name, int age, string city) { string query = "select value dog from Entities.DogSet where dog.Owner.Address.City == @City "; ObjectQuery oQuery = new ObjectQuery(query, YourContext); oQuery.Parameters.Add(new ObjectParameter("City", city)); List dogs = oQuery.ToList(); if(!String.IsNullOrEmpty(name)) dogs = dogs.Where(it => it.Name == name); if(age > 0) dogs = dogs.Where(it => it.Age == age); return dogs; }

    Это особенно полезно, когда вы начинаете отображать все данные, а затем разрешаете фильтрацию.

    Проблемы: - Может привести к серьезной передаче данных, если вы не будете осторожны относительно своего подмножества. - Вы можете фильтровать только данные, которые вы вернули. Это означает, что если вы не вернете ассоциацию Dog.Owner, вы не сможете фильтровать на Dog.Owner.Name. Какое же самое лучшее решение? Нет. Вам нужно выбрать решение, которое наилучшим образом подходит для вас и вашей проблемы: - Используйте построение запросов на основе лямбда, когда вам не нужны предварительная компиляция ваших запросов. - Используйте полностью определенный предварительно скомпилированный запрос Linq, когда ваша структура объекта не слишком сложна. - Используйте конкатенацию EntitySQL / string, когда структура может быть сложной, и когда возможное количество различных результирующих запросов невелико (что означает меньшее количество попыток предварительной компиляции). - Используйте фильтрацию в памяти, когда вы работаете с небольшим подмножеством данных или когда вам приходилось сначала получать все данные по данным в любом случае (если производительность в порядке со всеми данными, тогда фильтрация в памяти не будет вызывают любое время, потраченное на db).

    Доступ к Singleton

    Лучший способ справиться с вашим контекстом и сущностями на всех ваших страницах - использовать шаблон singleton:

    Public sealed class YourContext { private const string instanceKey = "On3GoModelKey"; YourContext(){} public static YourEntities Instance { get { HttpContext context = HttpContext.Current; if(context == null) return Nested.instance; if (context.Items == null) { On3GoEntities entity = new On3GoEntities(); context.Items = entity; } return (YourEntities)context.Items; } } class Nested { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Nested() { } internal static readonly YourEntities instance = new YourEntities(); } }

    NoTracking, это того стоит?

    При выполнении запроса вы можете указать системе отслеживать возвращаемые объекты или нет. Что это значит? С включенным отслеживанием (опция по умолчанию) инфраструктура будет отслеживать, что происходит с объектом (была ли она изменена? Создана? Удалена?), А также свяжет объекты вместе, когда из базы данных будут сделаны дополнительные запросы, что и есть представляет интерес здесь.

    Например, допустим, что у собаки с ID == 2 есть владелец, который ID == 10.

    Dog dog = (from dog in YourContext.DogSet where dog.ID == 2 select dog).FirstOrDefault(); //dog.OwnerReference.IsLoaded == false; Person owner = (from o in YourContext.PersonSet where o.ID == 10 select dog).FirstOrDefault(); //dog.OwnerReference.IsLoaded == true;

    Если мы будем делать то же самое без отслеживания, результат будет другим.

    ObjectQuery oDogQuery = (ObjectQuery) (from dog in YourContext.DogSet where dog.ID == 2 select dog); oDogQuery.MergeOption = MergeOption.NoTracking; Dog dog = oDogQuery.FirstOrDefault(); //dog.OwnerReference.IsLoaded == false; ObjectQueryOPersonQuery = (ObjectQuery) (from o in YourContext.PersonSet where o.ID == 10 select o); oPersonQuery.MergeOption = MergeOption.NoTracking; Owner owner = oPersonQuery.FirstOrDefault(); //dog.OwnerReference.IsLoaded == false;

    Отслеживание очень полезно и в идеальном мире без проблем с производительностью, он всегда будет включен. Но в этом мире есть цена за это, с точки зрения производительности. Итак, следует ли использовать NoTracking для ускорения работы? Это зависит от того, для чего вы собираетесь использовать данные.

    Есть ли вероятность, что данные вашего запроса с помощью NoTracking можно использовать для создания / вставки / удаления в базе данных? Если это так, не используйте NoTracking, потому что ассоциации не отслеживаются и будут вызывать исключения.

    На странице, где абсолютно нет обновлений базы данных, вы можете использовать NoTracking.

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

    Dog dog1 = (from dog in YourContext.DogSet where dog.ID == 2).FirstOrDefault(); ObjectQuery oDogQuery = (ObjectQuery) (from dog in YourContext.DogSet where dog.ID == 2 select dog); oDogQuery.MergeOption = MergeOption.NoTracking; Dog dog2 = oDogQuery.FirstOrDefault();

    dog1 и dog2 - это два разных объекта, один - гусеничный, а другой нет. Использование отделяемого объекта в обновлении / вставке заставит Attach (), который скажет «Подождите минуту, у меня уже есть объект здесь с тем же ключом базы данных. Fail». И когда вы присоединяете () один объект, вся его иерархия также привязана, что вызывает проблемы во всем мире. Будьте осторожны.

    Насколько быстрее это происходит с NoTracking

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

    Поэтому я должен везде использовать NoTracking?

    Не совсем. Есть некоторые преимущества для отслеживания объекта. Первый заключается в том, что объект кэшируется, поэтому последующий вызов этого объекта не попадет в базу данных. Этот кеш действителен только для времени жизни объекта YourEntities, который, если вы используете вышеописанный одноэлементный код, совпадает с временем жизни страницы. Один запрос страницы == один объект YourEntity. Поэтому для нескольких вызовов для одного и того же объекта он будет загружаться только один раз на страницу запроса. (Другой механизм кеширования может расширить это).

    Что происходит, когда вы используете NoTracking и пытаетесь загрузить один и тот же объект несколько раз? База данных будет запрашиваться каждый раз, поэтому есть влияние. Как часто вы / вызываете один и тот же объект во время запроса одной страницы? Как минимум, конечно, но это происходит.

    Также помните, что выше было указано, что ассоциации связаны автоматически для вашего? У вас нет этого с NoTracking, поэтому, если вы загружаете свои данные несколькими партиями, у вас не будет ссылки на них:

    ObjectQuery oDogQuery = (ObjectQuery)(from dog in YourContext.DogSet select dog); oDogQuery.MergeOption = MergeOption.NoTracking; List dogs = oDogQuery.ToList(); ObjectQueryOPersonQuery = (ObjectQuery)(from o in YourContext.PersonSet select o); oPersonQuery.MergeOption = MergeOption.NoTracking; ListOwners = oPersonQuery.ToList();

    В этом случае ни одна собака не будет иметь свой собственный набор свойств.

    Некоторые вещи следует иметь в виду, когда вы пытаетесь оптимизировать производительность.

    Нет ленивой загрузки, что мне делать?

    Это можно рассматривать как замаскированное благословение. Конечно, это раздражает, чтобы загрузить все вручную. Тем не менее, это уменьшает количество вызовов в db и заставляет вас думать, когда вы должны загружать данные. Чем больше вы можете загружать в одном вызове базы данных, тем лучше. Это всегда было правдой, но теперь она применяется с этой «особенностью» EF.

    Конечно, вы можете вызвать if (! ObjectReference.IsLoaded) ObjectReference.Load (); если вы хотите, но лучше всего заставить платформу загружать объекты, которые, как вы знаете, вам понадобятся одним выстрелом. Именно здесь начинается дискуссия о параметризованных включениях.

    Допустим, у вас есть объект Dog

    Public class Dog { public Dog Get(int id) { return YourContext.DogSet.FirstOrDefault(it => it.ID == id); } }

    Это тип функции, с которой вы работаете все время. Он вызывается со всех сторон, и как только у вас есть объект Dog, вы будете делать с ним разные вещи в разных функциях. Во-первых, он должен быть предварительно скомпилирован, потому что вы будете называть это очень часто. Во-вторых, каждая страница будет иметь доступ к другому подмножеству данных Собаки. Некоторым захочется Владелец, некоторые из них - FavoriteToy и т. Д.

    Конечно, вы можете вызвать Load () для каждой ссылки, в которой вы нуждаетесь, когда захотите. Но это вызовет вызов в базу данных каждый раз. Плохая идея. Поэтому вместо этого каждая страница будет запрашивать данные, которые она хочет увидеть, когда она сначала запрашивает объект Dog:

    Static public Dog Get(int id) { return GetDog(entity,"");} static public Dog Get(int id, string includePath) { string query = "select value o " + " from YourEntities.DogSet as o " +



    Рекомендуем почитать

    Наверх