Класс EntityObject

86

»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ

Сущностные типы, создаваемые в сущностной модели данных для представления схемы базы данных, наследуются от класса EntityObject, который является частью пространства имен System.Data.Objects.DataClasses.

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

Существует только один прототип конструктора, где T — сущностный тип:

public Т();

В следующем коде демонстрируется создание нового экземпляра сущностного типа Customer, заполнение полей данных и сохранение его в базе данных посредством метода SaveChanges класса ObjectContext:

NorthwindEntities context = new NorthwindEntities();

            // Создать новый объект Customer
            Customer cust = context.CreateObject<Customer>();

            // Заполнить все поля
            cust.CustomerID = "LAWN";
            cust.CompanyName = "Lawn Wranglers";
            cust.ContactName = "Mr. Abe Henry";
            cust.ContactTitle = "Owner";
            cust.Address = "1017 Maple Leaf Way";
            cust.City = "Ft. Worth";
            cust.Region = "TX";
            cust.PostalCode = "76104";
            cust.Country = "USA";
            cust.Phone = "(800) MOW-LAWN";
            cust.Fax = "(800) MOW-LAWO";

            context.AddObject("Customers", cust);

            context.SaveChanges();

Фабричный метод

Статический фабричный метод добавляется к сущностным типам, когда они создаются мастером Entity Data Model Wizard. Фабричный метод может использоваться для создания новых экземпляров сущностного типа и имеет преимущество перед конструктором по умолчанию, требуя значений для всех полей с обязательными значениями в базе данных. Это позволяет изящно избежать проблемы создания нового экземпляра, которому не хватает одного из таких значений.

Проблема связана с генерацией исключения при попытке сохранить экземпляр в базе данных методом SaveChanges. Как и с конструктором по умолчанию, сущностные объекты, создаваемые фабричными методами, не сохраняются до тех пор, пока они не будут добавлены к одной из коллекций сущностных типов, поддерживаемых производным от ObjectContext классом (для примера просмотрите метод AddObject класса ObjectContext).

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

public static T CreateT();

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

public static MуType CreateMyType();

Если сущностный тип представляет строки таблицы, содержащей столбцы, которые не могут иметь значения null, то у него будет предусмотрено по аргументу для каждого обязательного значения данных. Например, если посмотреть на таблицу Customers базы данных Northwind в SQL Server Management Studio, то можно заметить, что столбцы CustomerID и CompanyName не имеют отмеченных флажков Allow Nulls (Допускает значения null):

Таблица Customers базы данных Northwind

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

public static Customer CreateCustomer(String customerID, String CompanyName);

Простейший способ работы с прототипом фабричного метода предусматривает использование средства IntelliSense среды Visual Studio или даже обращение к исходному коду сущностного типа.

В коде, приведенном ниже, создается новый экземпляр сущностного типа Customer с помощью фабричного метода, которому передаются значения для обязательных полей данных. Затем устанавливаются остальные поля данных (хотя для них можно оставить значения по умолчанию). Новый объект затем добавляется к коллекции сущностей Customers, поддерживаемой производным классом ObjectContext и сохраняемой вызовом SaveChanges:

NorthwindEntities context = new NorthwindEntities();

// Создать новый объект Customer
Customer cust = Customer.CreateCustomer("LAWN", "Lawn Wranglers");

// Заполнить поля, допускающие null
cust.ContactName = "Mr. Abe Henry";
cust.ContactTitle = "Owner";
cust.Address = "1017 Maple Leaf Way";
cust.City = "Ft. Worth";
cust.Region = "TX";
cust.PostalCode = "76104";
cust.Country = "USA";
cust.Phone = "(800) MOW-LAWN";
cust.Fax = "(800) MOW-LAWO";

context.AddObject("Customers", cust);

context.SaveChanges();

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

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

Набор свойств, которые имеет сущностный тип, зависит от проектного решения таблицы базы данных, с которой он ассоциирован. Общий прототип показан ниже, где T — тип данных, a ColumnName — имя поля данных:

public T ColumnName{get; set;}

От общего прототипа толку мало. Намного полезнее взглянуть на действительную реализацию. На рисунке выше были показаны столбцы таблицы Customer в базе данных Northwind, строки которой представлены сущностным типом Customer в сущностной модели данных. Для каждого из столбцов, показанных на рисунке, будет найдено общедоступное свойство, позволяющее получать и устанавливать ассоциированные значения данных. Например, столбец City будет иметь следующий прототип:

public String City {get; set;}

В большинстве примеров по LINQ to Entities, используются примитивные свойства в той или иной форме. Код в следующем примере читает значение свойства City из сущностного объекта Customer, модифицирует значение и сохраняет изменение в базе данных, используя метод SaveChanges. Изменения значений свойств не записываются в базу данных до вызова метода SaveChanges:

NorthwindEntities context = new NorthwindEntities();

            // Запросить запись Customer
            Customer cust = (from c in context.Customers
                             where c.CustomerID == "LAZYK"
                             select c).First();

            // Получить доступ к текущему значению данных
            Console.WriteLine("Исходное значение City: {0}", cust.City);

            // Изменить значение
            cust.City = "Seattle";

            Console.WriteLine("Новое значение City: {0}", cust.City);

            // Сохранить изменения
            context.SaveChanges();

После компиляции и запуска кода получается следующий результат:

Чтение и изменение значения примитивного свойства

Навигационные свойства

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

Предположим, что необходимо найти набор заказов, которые заказчик разместил в базе данных Northwind, и единственные данные, от которых можно оттолкнуться — это название компании. Без навигационных свойств пришлось бы выполнять два запроса LINQ — один для нахождения сущностного объекта Customer, а другой для получения всех сущностных объектов Order, связанных по внешнему ключу с найденным ранее Customer. Ниже показано, как это могло бы работать:

NorthwindEntities context = new NorthwindEntities();

            // Запросить запись Customer
            Customer cust = (from c in context.Customers
                             where c.CompanyName == "Lazy K Kountry Store"
                             select c).First();

            // Запросить заказы, связанные с этим заказчиком
            IQueryable<Order> orders = from o in context.Orders
                                       where o.CustomerID == cust.CustomerID
                                       select o;

            // Вывести на экран результаты
            foreach (Order ord in orders)
            {
                Console.WriteLine("Order ID {0}, Date {1}", ord.OrderID, ord.OrderDate);
            }

Компиляция и запуск этого кода даст следующий результат:

Запрос без навигационных свойств

Был получен нужный результат, но за счет использования навигационных свойств можно избежать явного выполнения второго запроса.

Для каждого отношения внешнего ключа в базе данных существует пара навигационных свойств в сущностной модели данных — по одному для каждого затронутого сущностного типа. Прототип для свойства зависит от множественности отношения. Если сущностный тип может быть связан с множеством экземпляров другого сущностного типа (как Customer из базы Northwind может быть связан с множеством экземпляров сущностного типа Order), то прототип будет выглядеть, как показано ниже, где T — связанный сущностный тип, a TableNameOfТ — имя таблицы базы данных, чьи записи представляет Т:

public EntityCollection<T> TableNameOfT {get; set;}

Описание прототипов проще разобрать на примере. В базе данных Northwind таблицы Customers и Orders разделяют отношение внешнего ключа, т.е. множество строк в Orders могут иметь внешний ключ, указывающий на одну строку в Customers. Строки таблицы Customers представлены сущностным типом Customer, а строки таблицы Orders — сущностным типом Order. Все это значит, что в типе Customer должно присутствовать навигационное свойство с показанным ниже прототипом:

public EntityCollection<Order> Orders {get; set;}

Если в отношении может быть не более одного связанного объекта, то прототип выглядит, как показано ниже, при этом T — это сущностный тип:

public EntityCollection<T> TReference {get; set;}

В случае сущностного типа Order базы данных Northwind может быть только один связанный объект Customer, поэтому прототип будет выглядеть так, как показано ниже:

public EntityReference<Customer> CustomerReference {get; set;}

Мастер Entity Data Model Wizard также создает встречное свойство для отношений подобного рода. Прототип выглядит, как показано ниже, при этом T — это связанный сущностный тип:

public Customer Customer {get; set;}

Ниже показано, как использовать навигационное свойство Orders в сущностном типе Customer базы данных Northwind для получения всех заказов определенного заказчика. Здесь решается та же самая задача, что и в предыдущем примере, но без необходимости в явном втором запросе. Под "явным" подразумевается, что данные все равно будут получены из базы данных, но навигационное свойство облегчает задачу кодирования:

NorthwindEntities context = new NorthwindEntities();

            // Запросить запись Customer
            Customer cust = (from c in context.Customers
                             where c.CompanyName == "Lazy K Kountry Store"
                             select c).First();

            // Запросить заказы, связанные с этим заказчиком
            System.Data.Objects.DataClasses.EntityCollection<Order> orders =
                cust.Orders;

            // Вывести на экран результаты
            foreach (Order ord in orders)
            {
                Console.WriteLine("Order ID {0}, Date {1}", ord.OrderID, ord.OrderDate);
            }

В этом коде явно использовался класс EntityCollection, но если посмотреть на ряд других примеров, посвященных LINQ to Entities, можно заметить, что повсюду свободно применялись навигационные свойства, но класс явно не объявлялся. После компиляции и запуска этого кода будет получен результат, который полностью совпадает с выводом кода из предыдущего примера.

Пройди тесты
Лучший чат для C# программистов