Инициализиране на базата данни с тестови данни. Създаване на MVC уеб приложение

Да създаваш обектен моделза база данни класовете трябва да бъдат съпоставени с обекти, съхранявани в базата данни. Има три начина за прилагане на такова прехвърляне: можете да зададете атрибути за съществуващи обекти, можете да използвате специален инструмент, който ви позволява автоматично да генерирате обекти и да използвате помощната програма команден ред SQLMetal.

  • Обектно-релационен конструктор. Този дизайнер осигурява многофункционалност потребителски интерфейсза създаване на обектен модел от съществуваща база данни. Този инструмент, част от IDE на Visual Studio, е най-подходящ за малки до средни бази данни.
  • Инструмент за генериране на код SQLMetal. Според набора от параметри това конзолна програмадонякъде различен от Object-Relational Designer. Този инструмент е най-подходящ за моделиране на големи бази данни.
  • Редактор на код. Можеш да пишеш собствен кодИзползване на редактора на код на Visual Studio или друг редактор на код. Този подход може да бъде много податлив на грешки, така че ако имате съществуваща база данни, която може да се използва за създаване на модел с помощта на Object Relational Designer или SQLMetal, не се препоръчва. Редакторът на код обаче се превръща в ценен инструмент, когато трябва да прецизирате или промените кода, който вече сте създали с помощта на други инструменти.

Ние използваме последен методчрез задаване на атрибути за съществуващи обекти:

използване на системата; използване на System.Collections.Generic; използване на System.Linq; използване на System.Text; използване на System.Data.Linq; използване на System.Data.Linq.Mapping; използване на System.Data.SqlClient; пространство от имена LinqtoSQL ( публичен клас Клиент ( публичен низ CustomerID ( get; set; ) public string City ( get; set; ) public override string ToString() ( return CustomerID + "\t" + City; ) ) class Program ( static void Main (string args) ( DataContext db = new DataContext (@"Data Source=.\SQLEXPRESS; AttachDbFilename=|DataDirectory|\NORTHWND.MDF; Integrated Security=True; User Instance=True"); var results = from c in db. GetTable () където c.City == "Москва" изберете c;

Това приложение има клас Клиент, който показва таблицата Клиенти, и има полета CustomerID и City, които показват полетата в тази таблица. Обектът на клас DataContext указва входната точка в базата данни и има метод GetTable, който връща колекция от определен тип, в в този случайтип Клиент.

В резултат на изпълнение на програмата на екрана ще се покажат идентификаторите и градовете на пребиваване на тези клиенти, които живеят в Москва.

Съхранената процедура извлича 10-те най-скъпи продукта и техните цени от таблицата с продукти:

ИЗПОЛЗВАЙТЕ GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO ALTER процедура. AS SET ROWCOUNT 10 SELECT Products.ProductName AS TenMostExpensiveProducts, Products.UnitPrice FROM Products ORDER BY Products.UnitPrice DESC

За да извикате тази процедура от C# програма и да покажете резултатите, трябва да напишете само 3 реда код:

използване на системата; използване на System.Collections.Generic; използване на System.Linq; използване на System.Text; използване на System.Data.Linq; използване на System.Data.Linq.Mapping; namespace LinqtoSQL ( class Program ( static void Main(string args) ( var db = new northwindDataContext(); foreach (var r in db.Ten_Most_Expensive_Products()) Console.WriteLine(r.TenMostExpensiveProducts + "\t" + r.UnitPrice) ; Console.ReadKey();

Обърнете внимание, че входната точка към базата данни сега се създава от конструктора на класа northwindDataContext (като цяло класът ще се нарича (име на файл с карта) DataContext), на който вече не е необходимо да се предава изричен низ за връзка като параметър.

10.1.3. ADO.NET Entity Framework

ADO.NET Entity Framework(EF) – обектно-ориентирана технология за достъп до данни, е решение за обектно-релационно картографиране (ORM) за .NET Framework от Microsoft. Предоставя възможност за взаимодействие с обекти, използвайки както LINQ под формата на LINQ to Entities, така и използване на Entity SQL. За да се улесни изграждането на уеб решения, се използват както ADO.NET Data Services, така и комбинация от Windows Communication Foundation и Windows Presentation Foundation, което ви позволява да създавате многостепенни приложения чрез внедряване на един от шаблоните MVC дизайн, MVP или MVVM.

ADO.NET Entity Framework позволява на разработчиците да създават приложения за достъп до данни, които работят на концептуален модел на приложение, а не директно на релационна схема за съхранение. Целта му е да намали кода и усилията за поддръжка на приложения, ориентирани към данни. Приложенията на Entity Framework предоставят следните предимства.

  • приложенията могат да работят с концептуален модел по отношение на предметна област– включително с наследени типове, сложни елементи и връзки;
  • приложенията са освободени от строги зависимости от конкретно ядро ​​на СУБД или схема за съхранение;
  • съпоставянията между концептуалния модел и специфичната за хранилището схема могат да се променят без промяна на кода на приложението;
  • разработчиците са в състояние да работят с последователен обектен модел на приложение, който може да бъде картографиран към различни схеми за съхранение, които могат да бъдат внедрени в различни системиуправление на данни;
  • множество концептуални модели могат да бъдат съпоставени към една схема за съхранение;
  • поддръжката на интегрирани в език заявки (LINQ) осигурява проверка по време на компилиране на синтаксиса на заявката спрямо концептуален модел.
10.1.3.1. Компоненти на Entity Framework

Entity Framework е набор от ADO.NET технологии, които позволяват разработването на приложения, управлявани от данни. Архитектите и разработчиците на приложения, ориентирани към данни, трябва да обмислят постигането на две много различни цели. Те трябва да моделират обектите, връзките и логиката на решаваните бизнес проблеми, както и да работят с ядрата на СУБД, използвани за запазване и извличане на данни. Данните могат да бъдат разпределени в множество системи за съхранение, като всяка използва различни протоколи, но дори приложенията, работещи на една система за съхранение, трябва да балансират изискванията на системата за съхранение с изискванията за писане на ефективен, поддържаем код на приложение.

С Entity Framework разработчиците могат да работят с данни под формата на специфични за домейна обекти и свойства, като клиенти и техните адреси, без да се налага да имат достъп до базовите таблици и колони на базата данни, където се съхраняват тези данни. Тази възможност възниква благодарение на прехода към повече високо нивоабстракция, в която разработчиците могат да работят с данни, използвайки по-малко код, за да създават и поддържат приложения, ориентирани към данни.

Entity Framework е компонент на .NET Framework, така че приложенията на Entity Framework могат да работят на всеки компютър, на който е инсталиран .NET Framework 3.5 SP1.

10.1.3.1.1. Приложение на концептуалните модели в практиката

Отдавна известен и широко използван принцип на проектиране в моделирането на данни е моделът на данни да се раздели на следните три части: концептуален модел, логически модел и физически модел. Концептуалният модел дефинира обектите и връзките в системата, която се моделира. Логическият модел за релационна база данни нормализира обекти и връзки за създаване на таблици ограничения на външен ключ. IN физически моделвзема предвид възможностите на конкретна система за обработка на данни, като дефинира специфични за машината на базата данни детайли за съхранение, като разделяне и индексиране.

Физическият модел се усъвършенства от администраторите на бази данни, за да се подобри производителността, но програмистите, които разработват код на приложение, са до голяма степен ограничени до работа с логически модел, подготовка на SQL заявки и извикване на съхранени процедури. Концептуалните модели се използват предимно като инструмент за представяне и обмен на мнения относно изискванията на приложението и следователно най-често служат като до голяма степен неизменни диаграми, които се преглеждат и обсъждат в ранните етапи на проекта, след което вече не са във фокуса на вниманието.

Entity Framework дава смисъл на концептуалните модели, като позволява на разработчиците да правят заявки за обектите и връзките в концептуалния модел; самата Entity Framework обаче се използва за преобразуване на тези операции в команди, които са специфични за източника на данни. Това елиминира необходимостта приложенията да имат твърдо кодирани зависимости от конкретен източник на данни. Концептуалният модел, моделът за съхранение и тяхното картографиране са изразени във външна спецификация, известна също като модел на обект

Добър ден на всички Алексей Гулинин е във връзка. В предишни статии разгледахме различни подходи за работа с Entity Framework. В тази статия бих искал да ви кажа как работа с данни в Entity Framework. Помислете за следните операции:

  1. Добавяне на запис
  2. Четене на записи
  3. Редактиране на публикация
  4. Изтриване на запис

Нека създадем нов проект. Този път типът на проекта ще бъде " Windows приложениеФормуляри":

Нека добавим DataGridView елемент към нашата форма. Ще добавим и 3 бутона: „Добавяне“, „Редактиране“, „Изтриване“. Нека добавим още 2 елемента "TextBox", в които ще покажем информация за записа, който е избран в момента (тези 2 "TextBox" също ще се използват за добавяне на нов запис). Нека добавим още едно "TextBox", което ще показва информация за "ID" на записа (това ще е необходимо за редактиране на записа). Ще добавим и 2 елемента „етикет“. В крайна сметка нашата форма ще изглежда така:

Ще използваме подхода „Първо кодиране“. Нека създадем следния клас:

Публичен клас Държави ( public int Id ( get; set; ) public string Country ( get; set; ) public string Capital ( get; set; ) )

Основният код ще бъде във файла "Form1.cs". Ще ви дам целия код веднага, той ще бъде обсъден подробно в коментарите:

Използване на системата; използване на System.Windows.Forms; използване на System.Data.Entity; използване на System.Data.Entity.Migrations; пространство от имена 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(); от редове // Ако редът не е избран, тогава нищо не се случва, ако (dataGridView1.CurrentRow == null) return; // Вземете избрания ред и го присвоете на типа Държави = dataGridView1.CurrentRow.DataBoundItem като Държави; празен ред, тогава не правим нищо повече if (country == null) return; // Показване на данни за държавата и нейната столица в TextBox tb_Country.Text = country.Country; tb_Capital.Text = country.Capital;

tB_ID.Text = country.Id.ToString();

) // Добавяне на частен запис void btn_Add_Click(object sender, EventArgs e) ( // Проверете дали текстовите полета имат данни if (tb_Country.Text == String.Empty || tb_Capital.Text == String.Empty) ( MessageBox. Show("Fill in the country data!"); return; // Създайте екземпляр на класа Countries, // т.е. вземете данни за нашата страна от текстовите полета Countries country = new Countries ( Country = tb_Country.Text , Capital = tb_Capital. // Добавяме данни към нашата таблица db.Countries.Add(country); // Не забравяйте да запазите промените db.SaveChanges(); // Актуализирайте нашия dataGridView. нова държава dataGridView1.Refresh(); // Нулиране на текстови полета tb_Country.Text = String.Empty; tb_Capital.Text = String.Empty; tB_ID.Text = String.Empty;) // Редактиране на частен запис void btn_Edit_Click(object sender, EventArgs e) ( // Проверете дали записът е избран, ако (tB_ID.Text == String.Empty) се върне; // Вземете идентификатора от текстовото поле int id = Convert.ToInt32( tB_ID.Text); // Намиране на страната с помощта на метода Find() Country = db.Countries.Find(id); if (country == null) return; = tb_Capital.Text; / Добавяне или актуализиране на запис db.Countries.AddOrUpdate(country); db.SaveChanges(); dataGridView1.Refresh(); частен void btn_delete_Click(object sender, EventArgs e) ( if (tB_ID.Text == String.Empty) return; int id = Convert.ToInt32(tB_ID.Text); Държава = db.Countries.Find(id); if (country == null) return; страна.Държава = tb_Country.Text; ) ) )Както виждаме, всичко работи. Най-известният, функционален и широко използван ORM Най-известният, функционален и широко използванв света .NETне е необходимо, но развитието на проекта продължава много активно. От тази статия ще научите как бързо да започнете да използвате Entity Framework Core V ASP.NET Coreпроекти.

За да започнете с Най-известният, функционален и широко използвантрябва да инсталирате необходимите библиотеки. Нека добавим пакетите Microsoft.EntityFrameworkCore.SqlServer и Microsoft.EntityFrameworkCore.Tools.

Първо трябва да вземете решение за данните, които ще се съхраняват в базата данни. Ще добавя 2 класа User и Log, които ще отговарят за потребителските данни и някои log данни.

Публичен клас User ( public Guid Id ( get; set; ) public string FirstName ( get; set; ) public string LastName ( get; set; ) public string Email ( get; set; ) ) public class Log ( public long Id ( get ; set;) public DateTime UtcTime ( get; set; ) public string Data (get; set; ) )

След това можете да създадете контекст за работа с базата данни:

Клас TestDbContext: DbContext ( public TestDbContext(DbContextOptions options) : base(options) ( ) protected override void OnModelCreating(ModelBuilder modelBuilder) ( modelBuilder.Entity() .HasIndex(b => b.Email); ) public DbSet Users ( get; set; ) public DbSet Logs ( get; set; ) )

DbContext - този клас дефинира контекста на данните, използван за работа с базата данни.

DbSet - Представлява колекция от всички обекти от определен тип, които се съдържат в контекст или могат да бъдат заявени от база данни.

Сега трябва да настроите връзка с базата данни. За да направите това, отворете файла Startup.cs и добавете реда към метода ConfigureServices:

Services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Ще зададем низа за връзка в конфигурационния файл, по подразбиране е appsettings.json

"ConnectionStrings": ("DefaultConnection": "CONNECTION_STRING_HERE")

Създаване на база данни

Да отворим Конзола за управление на пакети

И изпълнете командата Add-Migration InitialCreate

Това ще създаде файловете, необходими за създаване на структурата на базата данни. Създадените файлове могат да се видят в създадената директория Migrations.

След това изпълнете командата Update-Database и базата данни ще бъде създадена.

Добавяне на регистриране на заявки

Сега нека добавим регистриране за всички APIзаявки. За да направите това, добавете класа LoggingMiddleware със следното съдържание:

Публичен клас LoggingMiddleware ( privateonly readonly RequestDelegate _next; private readonly ILogger _logger; public LoggingMiddleware(RequestDelegate next, ILogger logger) ( _next = next; _logger = logger; ) public async Task Invoke(HttpContext context, TestDbContext dbcontext) ( if (context != null && context.Request.Path.ToString().ToLower().StartsWith("/api")) ( using (var loggableResponseStream = new MemoryStream()) ( var originalResponseStream = context.Response.Body; context.Response.Body = loggableResponseStream; опитайте ( var request = await FormatRequest(context.Request); await _next(context); var response = await FormatResponse(loggableResponseStream); loggableResponseStream.Seek(0, SeekOrigin.Begin); var newLog = new Log ( Path = HttpUtility.UrlDecode(context.Request.Path + context.Request.QueryString), UtcTime = DateTime.UtcNow, Data = заявка, Response = response, StatusCode = context.Response.StatusCode, );

await dbcontext.Logs.AddAsync(newLog);

dbcontext.SaveChanges();

) catch (Exception ex) ( //Тук можете да добавите хвърляне на регистриране на грешки; ) finally ( context.Response.Body = originalResponseStream; ) ) ) private static async Task FormatRequest(HttpRequest request) ( request.EnableRewind(); string responseBody = new StreamReader(request.Body).ReadToEnd(); request.Body.Position = 0; return responseBody; ) private static async Task FormatResponse(Stream loggableResponseStream) ( loggableResponseStream.Position = 0; var buffer = new byte; await loggableResponseStream.ReadAsync (buffer, 0, buffer.Length); върне JsonConvert.SerializeObject(Encoding.UTF8.GetString(buffer) )

И във файла Startup.cs в метода Configure ще добавим:

App.UseMiddleware();

След това всички заявки, които започват с /api, ще бъдат регистрирани в базата данни. Тук си струва да се отбележи, че може да има много опции за регистриране. Тук представяме само една от опциите, която е лесна за изпълнение и не изисква използването на библиотеки или услуги на трети страни. Също така ще добавим контролер за работа с потребители и ще добавим метод за получаване на информация за всички потребители., което ще ви позволи бързо да започнете работа с Entity Framework Core.

Приятно програмиране.

Последна актуализация: 31.10.2015 г

Entity Framework е специална обектно-ориентирана технология, базирана на .NET framework за работа с данни. Докато традиционните инструменти на ADO.NET ви позволяват да създавате връзки, команди и други обекти за взаимодействие с бази данни, Entity Framework е по-високо ниво на абстракция, което ви позволява да се абстрахирате от самата база данни и да работите с данни, независимо от типа на съхранение. Ако на физическо ниво оперираме с таблици, индекси, първични и външни ключове, то на концептуално ниво, което ни предлага Entity Framework, вече работим с обекти.

Първата версия на Entity Framework - 1.0 беше пусната през 2008 г. и предостави много ограничена функционалност, основна поддръжка ORM (object-relational mapping - картографиране на данни към реални обекти) и един единствен подход за взаимодействие с базата данни - Database First. С пускането на версия 4.0 през 2010 г. много се промени - оттогава Entity Framework се превърна в препоръчителната технология за достъп до данни, а в самата рамка бяха въведени нови възможности за взаимодействие с базата данни - Model First и Code Първи подходи.

Последваха допълнителни функционални подобрения с пускането на версия 5.0 през 2012 г. И накрая, през 2013 г. беше пусната Entity Framework 6.0, която има възможност за асинхронен достъп до данни.

Централната концепция на Entity Framework е концепцията за обект. Един обект представлява набор от данни, свързани с конкретен обект. Ето защо тази технологиявключва работа не с таблици, а с обекти и техните набори.

Всяко образувание, като всеки обект от реалния свят, има редица свойства. Например, ако даден обект описва човек, тогава можем да идентифицираме свойства като собствено име, фамилия, височина, възраст, тегло. Свойствата не представляват непременно прости данни тип int, но може да представлява и по-сложни структури от данни. И всеки обект може да има едно или повече свойства, които ще разграничат този обект от другите и ще дефинират еднозначно този обект. Такива свойства се наричат ​​ключове.

В този случай обектите могат да бъдат свързани чрез асоциативни връзки "един към много", "един към един" и "много към много", точно както в реална база данни комуникацията се осъществява чрез външни ключове.

Отличителна черта на Entity Framework е използването на LINQ заявки за извличане на данни от базата данни. С помощта на LINQ можем не само да извличаме конкретни редове, съхраняващи обекти от базата данни, но и да извличаме обекти, свързани с различни асоциативни връзки.

Друга ключова концепция е Entity Data Model. Този модел картографира класове обекти към действителни таблици в базата данни.

Моделът на данни на обекта се състои от три нива: концептуално, ниво на съхранение и ниво на картографиране.

На концептуално ниво се дефинират класовете обекти, използвани в приложението.

Нивото за съхранение дефинира таблиците, колоните, връзките между таблиците и типовете данни, към които е нанесена използваната база данни.

Слоят за картографиране посредничи между предишните два, като определя картографирането между свойствата на класа на обекта и колоните на таблицата.

Така можем да взаимодействаме с таблици от базата данни чрез класове, дефинирани в приложението.

Начини за взаимодействие с базата данни

Entity Framework предполага три възможни начинивзаимодействие с базата данни:

    Първо базата данни: Entity Framework създава набор от класове, които отразяват модела на конкретна база данни

    Първо модел: разработчикът първо създава модел на база данни, от който Entity Framework след това създава действителната база данни на сървъра.

    Първо кодирайте: разработчикът създава клас на модел на данни, който ще се съхранява в базата данни, а след това Entity Framework генерира базата данни и нейните таблици, използвайки този модел

Нека приемем доста типичен сценарий - добавяне на много обекти към базата данни:

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

Постоянно прекомпилиране на някои заявки

Започвайки с Entity Framework 5, заявките автоматично се кешират след компилация, което може значително да ускори последващите изпълнения - текст SQL заявкаще бъдат взети от кеша, всичко, което остава, е да замените необходимите стойности на параметрите. Но има няколко ситуации, при които компилирането ще се извършва при всяко изпълнение.

Използване на Contains над колекция в паметта

На практика често има нужда да се добави условие към заявка, подобно на оператора SQL IN – да се провери дали стойността на дадено свойство съвпада с някой от елементите на колекцията. Например така:

списък канали = нов списък (1, 5, 9); dataContext.Entities .AsNoTracking() .Where(e => channels.Contains(e.Channel)) .ToList();
Този израз в крайна сметка се преобразува в SQL по следния начин:

ИЗБЕРЕТЕ. КАТО, . КАТО, . КАТО ОТ. КАТО КЪДЕ. IN (1, 5, 9)
Оказва се, че за оператора IN параметрите не се използват, а вместо това се заместват самите стойности. Няма да е възможно да се кешира такава заявка, защото Когато използвате колекцията с друго съдържание, текстът на заявката ще трябва да се генерира повторно. Това, между другото, засяга не само производителността на самата Entity Framework, но и сървъра на базата данни, тъй като за всеки нов списък от стойности в оператора IN сървърът ще трябва да възстанови и кешира плана за изпълнение.

Ако колекцията, за която се прави Contains, не се очаква голям бройелементи (да кажем, не повече от сто), проблемът може да бъде решен чрез динамично генериране на условия, свързани с оператора ИЛИ. Това е лесно да се направи, например с помощта на библиотеката LinqKit:

списък канали = нов списък (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);
В резултат на това получаваме вече параметризирана заявка:

ИЗБЕРЕТЕ. КАТО, . КАТО, . КАТО ОТ. КАТО КЪДЕ. IN (@p__linq__0,@p__linq__1,@p__linq__2)
Въпреки че динамична конструкциязаявката изглежда като допълнителна скъпа работа на практика, отнема сравнително малко процесорно време; В един проблем от реалния живот изграждането на заявка отнема повече от секунда при всяко извикване. А замяната на Contains с подобен динамичен израз намали времето за обработка на заявката (с изключение на първата) до десетки милисекунди.

Използване на Take and Skip

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

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

ИЗБЕРЕТЕ. КАТО, . КАТО, . КАТО ОТ. КАТО ПОРЪЧКА ОТ . ASC ОТСТЪПКА 10 РЕДА ИЗВЛЕЧЕ САМО СЛЕДВАЩИТЕ 10 РЕДА
Както размерът на страницата, така и стойността на отместването са посочени в заявката като константи, а не параметри. Това отново показва, че текстът на заявката няма да бъде кеширан. За щастие, започвайки с Entity Framework 6, има прост начин за заобикаляне на този проблем - използване на ламбда изрази във функциите Take и Skip:

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

ИЗБЕРЕТЕ. КАТО, . КАТО, . КАТО ОТ. КАТО ПОРЪЧКА ОТ . ASC ОТСТЪПКА @p__linq__0 РЕДОВЕ ИЗВЛЕЧЕ СЛЕДВАЩ @p__linq__1 САМО РЕДОВЕ

Голям брой включвания в една заявка

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

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

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

ИЗБЕРЕТЕ. КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . 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 уникален идентификатор) ​​AS , CAST (NULL AS varchar(1)) AS , CAST (NULL AS uniqueidentifier) ​​​​AS . AS (NULL AS uniqueidentifier) ​​​​AS , .AS FROM .AS ON = ) AS ORDER BY . ASC, . A.S.C.
Би било логично да се предположи, че 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(); деца2.Зареждане(); var query = c.ParentEntities .Where(e => e.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .ToList();
Оказва се, че за всяка дъщерна колекция четем всички обекти, които са свързани с родителските обекти, които попадат под критериите на заявката. След като се извика Load(), обектите се добавят към контекста. Докато чете родителските обекти, Entity Framework ще намери всички дъщерни обекти, които са в контекста, и съответно ще добави препратки към тях.

Основният недостатък тук е, че всяка заявка изисква отделно извикване към сървъра на базата данни. За щастие има начин да решите и този проблем. Библиотеката EntityFramework.Extended има способността да създава "бъдещи" заявки. Основната идея е, че всички заявки, за които е извикан методът за разширение 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();
В резултат, както в първия пример, обектите от колекцията с резултати ще съдържат правилно попълнени колекции Children1 и Children2 и всички данни ще бъдат получени в едно извикване на сървъра.

Използването на „бъдещи“ заявки ще бъде полезно във всяка ситуация, в която трябва да изпълните множество отделни заявки.

Изваждане на полета само от основния обект, когато се използва съпоставяне на таблица за тип

Нека си представим система, в която редица обекти имат базов клас, съдържащ техните общи характеристики (име, дата на създаване, собственик, статус и т.н.). Има също така изискване за прилагане на търсене за тези характеристики и показване на списък с резултати. Картографирането отново предполага използването само на основни характеристики.

От гледна точка на гъвкавостта на модела, картографирането Table Per Type е много подходящо за тази задача, където се създава отделна таблица за всеки тип. Например, имаме базов клас превозно средство и наследници - лек автомобил, камион, мотоциклет. В този случай в базата данни ще бъдат създадени четири таблици.

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

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

ИЗБЕРЕТЕ СЛУЧАЙ КОГАТО ((NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND (. IS NOT NULL))) AND (NOT ((. = 1) AND ( . НЕ Е NULL)))) ТОГАВА "0X" WHEN ((. = 1) И (. IS NOT NULL)) THEN "0X0X" WHEN ((. = 1) AND (. IS NOT NULL)) THEN "0X1X" ELSE "0X2X" END 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) И (. 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 . КРАЙ КАТО ОТ . AS ЛЯВО ВЪНШНО СЪЕДИНЕНИЕ (SELECT . AS , . AS , cast(1 като бит) AS FROM . AS ) AS ON . = .
Оказва се, че имаме нужда само от основна информация, а 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();
И всичко става много по-просто:

ИЗБЕРЕТЕ 1 КАТО , . КАТО, . КАТО, . КАТО ОТ. КАТО КЪДЕ. >= (DATEADD (ден, -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, Име = v.Name, ServiceTickets = v.ServiceTickets )).ToList();
И SQL, генериран за него:

ИЗБЕРЕТЕ. КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . КАТО, . 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 . LEFT OUTER JOIN . ASC, . ASC, . ASC, . ASC, . A.S.C.
Създадох тикет за 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, който ясно се предлага за търсене по идентификатори.

Бих формулирал общия подход за решаване на проблема по следния начин: ако е възможно да не се използва таблица за съпоставяне на тип, по-добре е да не се използва. В случаите, когато е трудно да се справите без него, трябва да опитате опциите, описани по-горе, и да видите дали ще ви донесат печалба.

Допълнителна информация

Свързаните с производителността нюанси, на които трябва да обърнете внимание, когато работите с Entity Framework (включително описаните в статията), са описани накратко на тази връзка: https://msdn.microsoft.com/en-us/data/hh949853.aspx. За съжаление не всички проблеми са посочени алтернативни решения, но информацията все още е много полезна. Трябва също да се отбележи, че поне точка 4.3 не е потвърдена на практика за Entity Framework 6.1.3.

Можете да помогнете и да прехвърлите средства за развитието на сайта