Поделиться через


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

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

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 в блоге. Это значение может быть вычисляется на основе 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;
}

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

Чтение значений несопоставленных свойств полезно при проверке сущностей 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);
}