Работа со значениями свойств

В большинстве случаев Entity Framework будет заботиться о отслеживании состояния, исходных значений и текущих значений свойств экземпляров сущностей. Однако в некоторых случаях, таких как отключенные сценарии, может потребоваться просмотреть или управлять информацией EF о свойствах. Методы, показанные в этом разделе, применяются одинаково к моделям, созданным с помощью code First и ef Designer.

Entity Framework отслеживает два значения для каждого свойства отслеживаемой сущности. Текущее значение, как следует из названия, это значение свойства в сущности. Исходное значение — это значение, которое было у свойства при запросе сущности из базы данных или присоединенном к контексту.

Существует два общих механизма работы со значениями свойств:

  • Значение одного свойства можно получить строго типизированным способом с помощью метода Property.
  • Значения для всех свойств сущности можно считывать в объект DbPropertyValues. Затем DbPropertyValues выступает в качестве объекта, подобного словарю, чтобы разрешить чтение и установка значений свойств. Значения в объекте DbPropertyValues можно задать из значений в другом объекте DbPropertyValues или из значений в другом объекте, например другой копии сущности или простого объекта передачи данных (DTO).

В следующих разделах показаны примеры использования обоих описанных выше механизмов.

Получение и настройка текущего или исходного значения отдельного свойства

В приведенном ниже примере показано, как можно считывать текущее значение свойства, а затем задать новое значение:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(3);

    // Read the current value of the Name property
    string currentName1 = context.Entry(blog).Property(u => u.Name).CurrentValue;

    // Set the Name property to a new value
    context.Entry(blog).Property(u => u.Name).CurrentValue = "My Fancy Blog";

    // Read the current value of the Name property using a string for the property name
    object currentName2 = context.Entry(blog).Property("Name").CurrentValue;

    // Set the Name property to a new value using a string for the property name
    context.Entry(blog).Property("Name").CurrentValue = "My Boring Blog";
}

Используйте свойство OriginalValue вместо свойства CurrentValue для чтения или задания исходного значения.

Обратите внимание, что возвращаемое значение вводится как объект, когда строка используется для указания имени свойства. С другой стороны, возвращаемое значение строго типизировано, если используется лямбда-выражение.

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

Если значение свойства задано таким образом, изменение обнаруживается автоматически, даже если функция AutoDetectChanges отключена.

Получение и установка текущего значения свойства, не имеющего сопоставления

Текущее значение свойства, которое не сопоставлено с базой данных, также можно считывать. Пример несопоставленного свойства может быть свойством RssLink в объекте Blog. Это значение может быть вычислено на основе BlogId, поэтому хранить его в базе данных не требуется. Рассмотрим пример.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);
    // Read the current value of an unmapped property
    var rssLink = context.Entry(blog).Property(p => p.RssLink).CurrentValue;

    // Use a string to specify the property name
    var rssLinkAgain = context.Entry(blog).Property("RssLink").CurrentValue;
}

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

Чтение значений несопоставленных свойств полезно при проверке несопоставленных свойств в Entity Framework. По той же причине текущие значения можно считывать и задавать для свойств сущностей, которые в настоящее время не отслеживаются контекстом. Рассмотрим пример.

using (var context = new BloggingContext())
{
    // Create an entity that is not being tracked
    var blog = new Blog { Name = "ADO.NET Blog" };

    // Read and set the current value of Name as before
    var currentName1 = context.Entry(blog).Property(u => u.Name).CurrentValue;
    context.Entry(blog).Property(u => u.Name).CurrentValue = "My Fancy Blog";
    var currentName2 = context.Entry(blog).Property("Name").CurrentValue;
    context.Entry(blog).Property("Name").CurrentValue = "My Boring Blog";
}

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

Проверка того, помечено ли свойство как измененное

В приведенном ниже примере показано, как проверить, помечено ли отдельное свойство как измененное:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    var nameIsModified1 = context.Entry(blog).Property(u => u.Name).IsModified;

    // Use a string for the property name
    var nameIsModified2 = context.Entry(blog).Property("Name").IsModified;
}

Значения измененных свойств отправляются в качестве обновлений в базу данных при вызове SaveChanges.

Маркировка свойства как измененного

В приведенном ниже примере показано, как принудительно пометить отдельное свойство как измененное:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    context.Entry(blog).Property(u => u.Name).IsModified = true;

    // Use a string for the property name
    context.Entry(blog).Property("Name").IsModified = true;
}

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

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

Чтение текущих, исходных и баз данных для всех свойств сущности

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

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Make a modification to Name in the tracked entity
    blog.Name = "My Cool Blog";

    // Make a modification to Name in the database
    context.Database.SqlCommand("update dbo.Blogs set Name = 'My Boring Blog' where Id = 1");

    // Print out current, original, and database values
    Console.WriteLine("Current values:");
    PrintValues(context.Entry(blog).CurrentValues);

    Console.WriteLine("\nOriginal values:");
    PrintValues(context.Entry(blog).OriginalValues);

    Console.WriteLine("\nDatabase values:");
    PrintValues(context.Entry(blog).GetDatabaseValues());
}

public static void PrintValues(DbPropertyValues values)
{
    foreach (var propertyName in values.PropertyNames)
    {
        Console.WriteLine("Property {0} has value {1}",
                          propertyName, values[propertyName]);
    }
}

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

Задание текущих или исходных значений из другого объекта

Текущие или исходные значения отслеживаемой сущности можно обновить путем копирования значений из другого объекта. Рассмотрим пример.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);
    var coolBlog = new Blog { Id = 1, Name = "My Cool Blog" };
    var boringBlog = new BlogDto { Id = 1, Name = "My Boring Blog" };

    // Change the current and original values by copying the values from other objects
    var entry = context.Entry(blog);
    entry.CurrentValues.SetValues(coolBlog);
    entry.OriginalValues.SetValues(boringBlog);

    // Print out current and original values
    Console.WriteLine("Current values:");
    PrintValues(entry.CurrentValues);

    Console.WriteLine("\nOriginal values:");
    PrintValues(entry.OriginalValues);
}

public class BlogDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}

При выполнении приведенного выше кода будет выведено следующее:

Current values:
Property Id has value 1
Property Name has value My Cool Blog

Original values:
Property Id has value 1
Property Name has value My Boring Blog

Этот метод иногда используется при обновлении сущности со значениями, полученными из вызова службы или клиента в n-уровневом приложении. Обратите внимание, что используемый объект не должен иметь тот же тип, что и сущность, если она имеет свойства, имена которых соответствуют именам сущности. В приведенном выше примере экземпляр BlogDTO используется для обновления исходных значений.

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

Установка текущих или исходных значений из словаря

Текущие или исходные значения отслеживаемой сущности можно обновить путем копирования значений из словаря или другой структуры данных. Рассмотрим пример.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    var newValues = new Dictionary<string, object>
    {
        { "Name", "The New ADO.NET Blog" },
        { "Url", "blogs.msdn.com/adonet" },
    };

    var currentValues = context.Entry(blog).CurrentValues;

    foreach (var propertyName in newValues.Keys)
    {
        currentValues[propertyName] = newValues[propertyName];
    }

    PrintValues(currentValues);
}

Используйте свойство OriginalValues вместо свойства CurrentValues, чтобы задать исходные значения.

Задание текущих или исходных значений из словаря с помощью свойства

Альтернативой использованию CurrentValues или OriginalValues, как показано выше, является использование метода Property для задания значения каждого свойства. Это может быть предпочтительнее, если необходимо задать значения сложных свойств. Рассмотрим пример.

using (var context = new BloggingContext())
{
    var user = context.Users.Find("johndoe1987");

    var newValues = new Dictionary<string, object>
    {
        { "Name", "John Doe" },
        { "Location.City", "Redmond" },
        { "Location.State.Name", "Washington" },
        { "Location.State.Code", "WA" },
    };

    var entry = context.Entry(user);

    foreach (var propertyName in newValues.Keys)
    {
        entry.Property(propertyName).CurrentValue = newValues[propertyName];
    }
}

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

Создание клонированного объекта, содержащего текущие, исходные или значения базы данных

Объект DbPropertyValues, возвращаемый из CurrentValues, OriginalValues или GetDatabaseValues, можно использовать для создания клона сущности. Этот клон будет содержать значения свойств из объекта DbPropertyValues, используемого для его создания. Рассмотрим пример.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();
}

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

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

Получение и настройка текущих или исходных значений сложных свойств

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

using (var context = new BloggingContext())
{
    var user = context.Users.Find("johndoe1987");

    // Get the Location complex object
    var location = context.Entry(user)
                       .Property(u => u.Location)
                       .CurrentValue;

    // Get the nested State complex object using chained calls
    var state1 = context.Entry(user)
                     .ComplexProperty(u => u.Location)
                     .Property(l => l.State)
                     .CurrentValue;

    // Get the nested State complex object using a single lambda expression
    var state2 = context.Entry(user)
                     .Property(u => u.Location.State)
                     .CurrentValue;

    // Get the nested State complex object using a dotted string
    var state3 = context.Entry(user)
                     .Property("Location.State")
                     .CurrentValue;

    // Get the value of the Name property on the nested State complex object using chained calls
    var name1 = context.Entry(user)
                       .ComplexProperty(u => u.Location)
                       .ComplexProperty(l => l.State)
                       .Property(s => s.Name)
                       .CurrentValue;

    // Get the value of the Name property on the nested State complex object using a single lambda expression
    var name2 = context.Entry(user)
                       .Property(u => u.Location.State.Name)
                       .CurrentValue;

    // Get the value of the Name property on the nested State complex object using a dotted string
    var name3 = context.Entry(user)
                       .Property("Location.State.Name")
                       .CurrentValue;
}

Используйте свойство OriginalValue вместо свойства CurrentValue, чтобы получить или задать исходное значение.

Обратите внимание, что для доступа к сложному свойству можно использовать метод Property или ComplexProperty. Однако метод ComplexProperty должен использоваться, если требуется детализировать сложный объект с помощью дополнительных вызовов Property или ComplexProperty.

Использование DbPropertyValues для доступа к сложным свойствам

При использовании CurrentValues, OriginalValues или GetDatabaseValues для получения всех текущих, исходных или исходных значений базы данных для сущности значения любых сложных свойств возвращаются в виде вложенных объектов DbPropertyValues. Затем эти вложенные объекты можно использовать для получения значений сложного объекта. Например, следующий метод выводит значения всех свойств, включая значения любых сложных свойств и вложенных сложных свойств.

public static void WritePropertyValues(string parentPropertyName, DbPropertyValues propertyValues)
{
    foreach (var propertyName in propertyValues.PropertyNames)
    {
        var nestedValues = propertyValues[propertyName] as DbPropertyValues;
        if (nestedValues != null)
        {
            WritePropertyValues(parentPropertyName + propertyName + ".", nestedValues);
        }
        else
        {
            Console.WriteLine("Property {0}{1} has value {2}",
                              parentPropertyName, propertyName,
                              propertyValues[propertyName]);
        }
    }
}

Чтобы распечатать все текущие значения свойств, метод будет вызываться следующим образом:

using (var context = new BloggingContext())
{
    var user = context.Users.Find("johndoe1987");

    WritePropertyValues("", context.Entry(user).CurrentValues);
}