Наследование сущностных классов

45

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

До сих пор при обсуждении LINQ to SQL всегда присутствовал единственный сущностный класс, отображаемый на единственную таблицу, для которой имелся такой отображаемый класс. Поэтому отображение между сущностными классами и таблицами до сих пор строилось по схеме "один к одному".

В примере, используемом в этой статье, создается модель данных, которая содержит классы Square и Rectangle.

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

В LINQ to SQL также предлагается альтернатива этому, которая называется наследованием сущностных классов. Наследование сущностных классов позволяет отобразить иерархию классов на единственную таблицу базы данных. Для этой единственной таблицы должен существовать базовый сущностный класс с соответствующими атрибутами отображения класса на таблицу базы данных. Этот базовый класс будет содержать все свойства, общие для всех классов в иерархии, унаследованных от базового класса, в то время как производные классы будут содержать лишь свойства, специфичные для этого производного класса, как это принято в любой объектной модели.

Ниже приведен пример базового сущностного класса без отображенных производных классов:

[Table]
public class Shape
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "Int NOT NULL IDENTITY")]
    public int Id;

    [Column(IsDiscriminator = true, DbType = "NVarChar(2)")]
    public string ShapeCode;

    [Column(DbType = "Int")]
    public int StartingX;

    [Column(DbType = "Int")]
    public int StartingY;
}

Как видите, здесь указан атрибут Table, и поскольку свойство Name этого атрибута не было задано, базовый сущностный класс отображается на одноименную с классом таблицу, т.е. на Shape. Не беспокойтесь, что таблицы Shape пока еще нет. Позже с помощью метода CreateDatabase объекта DataContext база данных будет создана. К этому моменту никакие производные классы не отображены. Позднее этот базовый сущностный класс будет участвовать при отображении некоторых производных классов.

Идея, положенная в основу наследования сущностных классов, заключается в том, что единственная таблица базы данных Shape содержит столбец, значение которого определяет, объект какого именно сущностного класса должен быть сконструирован из записи таблицы, когда он будет извлечен LINQ to SQL. Этот столбец называется столбцом дискриминатора, и он указывается с помощью свойства IsDiscriminator атрибута Column.

Значение столбца дискриминатора известно под названием значение дискриминатора или код дискриминатора. При отображении базового сущностного класса на таблицу базы данных в дополнение к атрибуту Table указываются атрибуты InheritanceMapping для отображения кодов дискриминатора на классы-наследники базового сущностного класса. Но на этот раз в предыдущем классе Shape никакое наследование не отображается.

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

В этом классе предусмотрены члены для Id, ShapeCode и начальных координат X и Y для фигуры на экране. Пока это единственные члены, наличие которых можно предвидеть в каждой фигуре.

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

public class Square : Shape
  {
    [Column(DbType = "Int")]
    public int Width;
  }

  public class Rectangle : Square
  {
    [Column(DbType = "Int")]
    public int Length;
  }

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

В этом примере с наследованием сущностных классов, поскольку стороны квадрата должны быть равны, необходимо значение только одного измерения — ширины. Так как прямоугольник требует ширину и длину, он наследуется от квадрата и добавляет к нему член для представления длины. В этом смысле с точки зрения наследования классов прямоугольник является квадратом, но квадрат — не есть прямоугольник. Хотя это противоположно геометрическому определению, оно вполне укладывается в модель наследования сущностных классов.

Общедоступные члены этих классов специфичны для каждого класса. Например, поскольку Square нужна ширина, он имеет свойство Width. Поскольку Rectangle наследуется от Square, в дополнение к унаследованному свойству Width ему понадобится свойство для представления длины — Length.

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

  [Table]
  [InheritanceMapping(Code = "G", Type = typeof(Shape), IsDefault = true)]
  [InheritanceMapping(Code = "S", Type = typeof(Square))]
  [InheritanceMapping(Code = "R", Type = typeof(Rectangle))]
  public class Shape
  {
    [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "Int NOT NULL IDENTITY")]
    public int Id;

    [Column(IsDiscriminator = true, DbType = "NVarChar(2)")]
    public string ShapeCode;

    [Column(DbType = "Int")]
    public int StartingX;

    [Column(DbType = "Int")]
    public int StartingY;
  }

Добавленные отображения связывают различные значения столбца дискриминатора с сущностными классами. Поскольку столбец ShapeCode является столбцом дискриминатора, если запись имеет значение "G" в этом столбце, то из этой записи конструируется объект класса Shape. Если же запись имеет значение "S" в столбце ShapeCode, то из нее конструируется объект класса Square. А если запись имеет значение "R" в этом столбце, то из этой записи конструируется объект Rectangle.

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

Поэтому, если запись имеет, скажем, значение "Q" в столбце ShapeCode, из этой записи по умолчанию будет сконструирован объект Shape, поскольку это значение не соответствует ни одному из заданных кодов дискриминатора.

Все изложенное почти полностью раскрывает концепцию отображения наследования сущностных классов. Теперь давайте рассмотрим полный код класса DataContext:

public partial class TestDB : DataContext
  {
    public Table<Shape> Shapes;

    public TestDB(string connection) :
      base(connection)
    {
    }

    public TestDB(System.Data.IDbConnection connection) :
      base(connection)
    {
    }

    public TestDB(string connection, System.Data.Linq.Mapping.MappingSource mappingSource) :
      base(connection, mappingSource)
    {
    }

    public TestDB(System.Data.IDbConnection connection, System.Data.Linq.Mapping.MappingSource mappingSource) :
      base(connection, mappingSource)
    {
    }
  }

Помимо помещения ранее упомянутых классов в класс [Your]DataContext по имени TestDB и добавления к нему некоторых конструкторов здесь нет ничего нового. В следующем примере вызывается код для создания базы данных:

TestDB db = new TestDB(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
                       Initial Catalog=TestDB;Integrated Security=SSPI;");
            db.CreateDatabase();

Этот код не дает никакого экранного вывода, но если заглянуть на сервер базы данных, можно увидеть, что в нем появилась база данных по имени TestDB с единственной таблицей Shapes. Проверьте таблицу Shape, чтобы удостовериться, что в ней нет записей. Теперь, имея таблицу, давайте занесем в нее некоторые данные, используя код LINQ to SQL:

TestDB db = new TestDB(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
                       Initial Catalog=TestDB;Integrated Security=SSPI;");

db.Shapes.InsertOnSubmit(new Square { Width = 4 });
db.Shapes.InsertOnSubmit(new Rectangle { Width = 3, Length = 6 });
db.Shapes.InsertOnSubmit(new Rectangle { Width = 11, Length = 5 });
db.Shapes.InsertOnSubmit(new Square { Width = 6 });
db.Shapes.InsertOnSubmit(new Rectangle { Width = 4, Length = 7 });
db.Shapes.InsertOnSubmit(new Square { Width = 9 });

db.SubmitChanges();

В этом коде также нет ничего нового. Создается объект DataContext, а затем объекты сущностных классов, которые вставляются в таблицу Shapes. Далее вызывается метод SubmitChanges, чтобы сохранить их в базе данных. После запуска этого кода в таблице Shapes базы данных TestDB должны появиться записи, представленные в след. таблице:

Результаты работы примера
Id ShapeCode StartingX StartingY Length Width
1 S 0 0 NULL 4
2 R 0 0 6 3
3 R 0 0 5 11
4 S 0 0 NULL 6
5 R 0 0 7 4
6 S 0 0 NULL 9

Поскольку Id — столбец идентичности, при многократном запуске этого кода его значения будут изменяться.

Теперь можно выполнять запросы к этой таблице.

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