Це стосується здебільшого програми 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, це те, що ви не можете призначити немодельований клас базовий клас. Навіть не намагайтеся, це перепише модельєр. То що робити?
Ви можете використовувати інтерфейси для забезпечення того, щоб класи реалізовували певну функціональність. Наприклад, ось інтерфейс 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.
Що таке скомпільований запит? Це просто запит, для якого ви вказуєте фреймворку зберігати розібране дерево в пам'яті, щоб його не потрібно було регенерувати під час наступного запуску. Отже, наступного запуску ви заощадите час, необхідний для синтаксичного аналізу дерева. Не варто скидати з рахунків, оскільки це дуже дорога операція, яка стає ще гіршою при складніших запитах.
Існує 2 способи компіляції запиту: створення ObjectQuery за допомогою EntitySQL та використання функції CompiledQuery.Compile (). (Зверніть увагу, що, використовуючи EntityDataSource на своїй сторінці, ви фактично будете використовувати ObjectQuery з EntitySQL, щоб він був скомпільований та кешований).
Окрім цього, якщо ви не знаєте, що таке EntitySQL. Це рядовий спосіб написання запитів щодо EF. Ось приклад: "виберіть значення dog з Entities.DogSet як dog where dog.ID = @ID". Синтаксис дуже схожий на синтаксис SQL. Ви також можете робити досить складні маніпуляції з об'єктами, що добре пояснено [тут] [1].
Гаразд, ось ось як це зробити за допомогою ObjectQuery <>
string query = "select value dog " +
"from Entities.DogSet as dog " +
"where dog.ID = @ID";
ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance));
oQuery.Parameters.Add(new ObjectParameter("ID", id));
oQuery.EnablePlanCaching = true;
return oQuery.FirstOrDefault();
Під час першого запуску цього запиту фреймворк генерує дерево виразів і зберігає його в пам'яті. Тож наступного разу, коли він буде виконаний, ви заощадите на цьому дорогому кроці. У цьому прикладі EnablePlanCaching = true, що є непотрібним, оскільки це варіант за замовчуванням.
Іншим способом компіляції запиту для подальшого використання є метод CompiledQuery.Compile. Тут використовується делегат:
static readonly Func<Entities, int, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
ctx.DogSet.FirstOrDefault(it => it.ID == id));
або за допомогою linq
static readonly Func<Entities, int, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
(from dog in ctx.DogSet where dog.ID == id select dog).FirstOrDefault());
щоб викликати запит:
query_GetDog.Invoke( YourContext, id );
Перевага CompiledQuery полягає в тому, що синтаксис вашого запиту перевіряється під час компіляції, де EntitySQL - ні. Однак є й інші міркування ...
Включає
Скажімо, ви хочете, щоб дані власника собаки були повернуті запитом, щоб уникнути здійснення 2 дзвінків до бази даних. Зробити це просто, так?
EntitySQL
string query = "select value dog " +
"from Entities.DogSet as dog " +
"where dog.ID = @ID";
ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)).Include("Owner");
oQuery.Parameters.Add(new ObjectParameter("ID", id));
oQuery.EnablePlanCaching = true;
return oQuery.FirstOrDefault();
CompiledQuery
static readonly Func<Entities, int, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
(from dog in ctx.DogSet.Include("Owner") where dog.ID == id select dog).FirstOrDefault());
А що, якщо ви хочете, щоб параметризовано Включити? Я маю на увазі те, що ви хочете мати єдину функцію Get (), яка викликається з різних сторінок, які піклуються про різні стосунки між собакою. Один піклується про власника, інший про його FavoriteFood, інший про його FavotireToy тощо. По суті, ви хочете сказати запиту, які асоціації завантажувати.
З EntitySQL це легко зробити
public Dog Get(int id, string include)
{
string query = "select value dog " +
"from Entities.DogSet as dog " +
"where dog.ID = @ID";
ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance))
.IncludeMany(include);
oQuery.Parameters.Add(new ObjectParameter("ID", id));
oQuery.EnablePlanCaching = true;
return oQuery.FirstOrDefault();
}
Включення просто використовує переданий рядок. Досить просто. Зверніть увагу, що можна вдосконалити функцію Include (рядок) (яка приймає лише один шлях) за допомогою IncludeMany (рядок), який дозволить вам передавати рядок розділених комами асоціацій для завантаження. Подивіться далі в розділі розширення для цієї функції.
Однак, якщо ми спробуємо зробити це за допомогою CompiledQuery, ми стикаємося з численними проблемами:
Очевидне
static readonly Func<Entities, int, string, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, string, Dog>((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: "Власник" та "FavoriteFood" (що не слід плутати з "Owner.FavoriteFood"!).
Тоді давайте використаємо IncludeMany (), який є функцією розширення
static readonly Func<Entities, int, string, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) =>
(from dog in ctx.DogSet.IncludeMany(include) where dog.ID == id select dog).FirstOrDefault());
Знову помилково, це тому, що EF не може проаналізувати IncludeMany, оскільки він не є частиною функцій, які розпізнає: це розширення.
Гаразд, отже, ви хочете передати довільну кількість шляхів до вашої функції, а Includes () займає лише один. Що робити? Ви можете вирішити, що вам ніколи не знадобиться більше ніж, скажімо, 20 Включає, і передати кожен відокремлений рядок у структурі до 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<Entities, int, ObjectQuery<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, int, string, ObjectQuery<Dog>>((ctx, id) =>
(ObjectQuery<Dog>)(from dog in ctx.DogSet where dog.ID == id select dog));
public Dog GetDog( int id, string include )
{
ObjectQuery<Dog> oQuery = query_GetDog(id);
oQuery = oQuery.IncludeMany(include);
return oQuery.FirstOrDefault;
}
Це мало б спрацювати, за винятком того, що коли ви викликаєте IncludeMany (або Include, Where, OrderBy ...), ви робите недійсним кешований скомпільований запит, оскільки зараз він абсолютно новий! Отже, дерево виразів потрібно проаналізувати, і ви знову отримаєте цей результат.
То яке рішення? Ви просто не можете використовувати CompiledQueries з параметризованими Включає. Замість цього використовуйте EntitySQL. Це не означає, що для CompiledQueries немає використання. Він чудово підходить для локалізованих запитів, які завжди будуть викликатися в одному контексті. В ідеалі завжди слід використовувати CompiledQuery, оскільки синтаксис перевіряється під час компіляції, але через обмеження це неможливо.
Прикладом використання може бути: ви можете мати сторінку, яка запитує, у яких двох собак однакова улюблена їжа, яка є трохи вузькою для функції BusinessLayer, тому ви розміщуєте її на своїй сторінці і точно знаєте, який тип включає вимагається.
Передача більше 3 параметрів до CompiledQuery
Функція обмежена 5 параметрами, з яких останній - це тип повернення, а перший - ваш об’єкт Entities із моделі. Отже, у вас залишається 3 параметри. Коментар, але його можна дуже легко покращити.
public struct MyParams
{
public string param1;
public int param2;
public DateTime param3;
}
static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((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<Dog> 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<Entities, int, string, IEnumerable<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) =>
from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog);
public IEnumerable<Dog> GetSomeDogs( int age, string name )
{
return query_GetDog(YourContext,age,name);
}
public void DataBindStuff()
{
IEnumerable<Dog> dogs = GetSomeDogs(4,"Bud");
// but I want the dogs ordered by BirthDate
gridView.DataSource = dogs.OrderBy( it => it.BirthDate );
}
Що тут буде відбуватися? Продовжуючи грати з оригінальним ObjectQuery (тобто фактичним типом повернення оператора Linq, який реалізує IEnumerable), він призведе до недійсності скомпільованого запиту та змусить його повторно проаналізувати. Отже, основним правилом є повернення натомість списку <> об’єктів.
static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) =>
from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog);
public List<Dog> GetSomeDogs( int age, string name )
{
return query_GetDog(YourContext,age,name).ToList(); //<== change here
}
public void DataBindStuff()
{
List<Dog> dogs = GetSomeDogs(4,"Bud");
// but I want the dogs ordered by BirthDate
gridView.DataSource = dogs.OrderBy( it => it.BirthDate );
}
Коли ви викликаєте ToList (), запит виконується відповідно до скомпільованого запиту, а потім, OrderBy виконується щодо об'єктів у пам'яті. Це може бути трохи повільніше, але я навіть не впевнений. Одне впевнене, що ви не турбуєтесь про неправильну обробку ObjectQuery та анулювання складеного плану запиту.
Ще раз, це не загальний вислів. ToList () - це трюк захисного програмування, але якщо у вас є вагома причина не використовувати ToList (), продовжуйте. Є багато випадків, коли ви хочете уточнити запит перед його виконанням.
Продуктивність
Який вплив складає запит на продуктивність? Він насправді може бути досить великим. Емпіричне правило полягає в тому, що складання та кешування запиту для повторного використання займає принаймні подвійний час простого виконання без кешування. Щодо складних запитів (читайте 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<Entities, MyParams, IEnumerable<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((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<Dog> GetSomeDogs()
{
MyParams myParams = new MyParams();
myParams.name = "Bud";
myParams.checkName = true;
myParams.age = 0;
myParams.checkAge = false;
return query_GetDog(YourContext,myParams).ToList();
}
Перевага тут полягає в тому, що ви отримуєте всі переваги попередньо складеного кверу. Недоліками є те, що ви, швидше за все, отримаєте пропозицію where, яку досить важко підтримувати, що ви будете штрафувати за попередню компіляцію запиту і що кожен виконаний вами запит не настільки ефективний, як може бути (особливо з приєднаними об'єднаннями).
Інший спосіб - це побудувати запит EntitySQL поштучно, як ми всі це робили з SQL.
protected List<Dod> 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<Dog> oQuery = new ObjectQuery<Dog>( 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<Dod> GetSomeDogs( string name, int age, string city)
{
string query = "select value dog from Entities.DogSet where dog.Owner.Address.City == @City ";
ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>( query, YourContext );
oQuery.Parameters.Add( new ObjectParameter( "City", city ) );
List<Dog> 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 / рядків, коли структура може бути складною і коли можлива кількість різних результуючих запитів невелика (що означає менше звернень до попередньої компіляції).
Одномісний доступ
Найкращий спосіб мати справу з вашим контекстом та сутностями на всіх ваших сторінках - використовувати шаблон однотонність:
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[instanceKey] == null)
{
On3GoEntities entity = new On3GoEntities();
context.Items[instanceKey] = entity;
}
return (YourEntities)context.Items[instanceKey];
}
}
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, чи варто це?
Виконуючи запит, ви можете сказати фреймворку відстежувати об’єкти, які він поверне чи ні. Що це означає? Якщо ввімкнено відстеження (опція за замовчуванням), фреймворк буде відслідковувати те, що відбувається з об’єктом (чи був він змінений? Створений? Видалений?), А також буде зв’язувати об’єкти разом, коли з бази даних будуть зроблені подальші запити, що представляє інтерес тут.
Наприклад, припустимо, що собака з ідентифікатором == 2 має власника, який має ідентифікатор == 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<Dog> oDogQuery = (ObjectQuery<Dog>)
(from dog in YourContext.DogSet where dog.ID == 2 select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
Dog dog = oDogQuery.FirstOrDefault();
//dog.OwnerReference.IsLoaded == false;
ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)
(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<Dog> oDogQuery = (ObjectQuery<Dog>)
(from dog in YourContext.DogSet where dog.ID == 2 select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
Dog dog2 = oDogQuery.FirstOrDefault();
dog1 і dog2 - це 2 різні об'єкти, один відстежується, а другий - ні. Використання відокремленого об'єкта в оновленні / вставці змусить Attach () сказати "Зачекайте хвилину, я вже маю тут об'єкт з тим самим ключем бази даних. Помилка". І коли ви прикріплюєте () один об’єкт, вся його ієрархія також приєднується, спричиняючи проблеми скрізь. Будьте особливо обережні.
Наскільки швидше це за допомогою NoTracking
Це залежить від запитів. Деякі набагато піддаються відстеженню, ніж інші. У мене немає швидкого простого правила, але це допомагає.
Тоді я повинен використовувати NoTracking скрізь тоді?
Не зовсім. Є кілька переваг відстеження об’єкта. Перший з них полягає в тому, що об’єкт кешований, тому наступний виклик цього об’єкта не потрапить у базу даних. Цей кеш дійсний лише протягом усього життя об’єкта YourEntities, який, якщо ви використовуєте синглтон-код вище, збігається із часом життя сторінки. Запит на одну сторінку == один об’єкт YourEntity. Отже, для кількох викликів одного і того ж об’єкта він завантажується лише один раз на запит сторінки. (Інший механізм кешування може це розширити).
Що відбувається, коли ви використовуєте NoTracking і намагаєтесь завантажити один і той же об'єкт кілька разів? База даних буде запитуватися кожного разу, тому це впливає. Як часто потрібно / слід дзвонити за одним і тим же об’єктом під час запиту однієї сторінки? Як можна менше, звичайно, але це трапляється.
Також пам’ятайте вищезазначений твір про автоматичне підключення асоціацій для вашого? У вас цього немає з NoTracking, тому, якщо ви завантажуєте свої дані кількома пакетами, у вас не буде посилання між ними:
ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)(from dog in YourContext.DogSet select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
List<Dog> dogs = oDogQuery.ToList();
ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)(from o in YourContext.PersonSet select o);
oPersonQuery.MergeOption = MergeOption.NoTracking;
List<Person> owners = oPersonQuery.ToList();
У цьому випадку жодна собака не матиме набору властивостей .Owner.
Деякі речі слід пам’ятати, намагаючись оптимізувати ефективність.
Ніякого ледачого завантаження, що мені робити?
Це можна розглядати як замасковане благословення. Звичайно, дратує завантажувати все вручну. Однак це зменшує кількість дзвінків на db і змушує думати про те, коли слід завантажувати дані. Чим більше ви можете завантажити в один дзвінок бази даних, тим краще. Це завжди було правдою, але це застосовується зараз із цією «особливістю» EF.
Звичайно, ви можете зателефонувати if (! ObjectReference.IsLoaded) ObjectReference.Load (); якщо ви хочете, але краща практика - змусити фреймворк завантажувати об’єкти, які, як ви знаєте, вам знадобляться одним пострілом. Тут дискусія про параметризований Includes починає мати сенс.
Скажімо, у вас є ви собака
public class Dog
{
public Dog Get(int id)
{
return YourContext.DogSet.FirstOrDefault(it => it.ID == id );
}
}
Це тип функції, з якою ви постійно працюєте. Йому дзвонять звідусіль, і як тільки у вас з’явиться цей об’єкт «Собака», ви будете робити з ним дуже різні речі в різних функціях. По-перше, його слід заздалегідь скомпілювати, тому що ви будете телефонувати дуже часто. По-друге, кожні різні сторінки хочуть мати доступ до іншого підмножини даних собаки. Хтось захоче Власника, хтось 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 " +