Compartir vía


Trabajar con valores de propiedad

En la mayoría de las partes, Entity Framework se encargará de realizar el seguimiento del estado, los valores originales y los valores actuales de las propiedades de las instancias de entidad. Sin embargo, puede haber algunos casos, como escenarios desconectados, donde desee ver o manipular la información que EF tiene sobre las propiedades. Las técnicas que se muestran en este tema se aplican igualmente a los modelos creados con Code First y EF Designer.

Entity Framework realiza un seguimiento de dos valores para cada propiedad de una entidad con seguimiento. El valor actual es, como indica el nombre, el valor actual de la propiedad en la entidad. El valor original es el valor que tenía la propiedad cuando se consultó la entidad desde la base de datos o se adjuntó al contexto.

Hay dos mecanismos generales para trabajar con valores de propiedad:

  • El valor de una sola propiedad se puede obtener con tipo mediante el método Property.
  • Los valores de todas las propiedades de una entidad se pueden leer en un objeto DbPropertyValues. DbPropertyValues actúa como un objeto similar al diccionario para permitir que los valores de propiedad se lean y establezcan. Los valores de un objeto DbPropertyValues se pueden establecer a partir de valores de otro objeto DbPropertyValues o de valores de algún otro objeto, como otra copia de la entidad o un objeto de transferencia de datos simple (DTO).

En las secciones siguientes se muestran ejemplos de uso de ambos mecanismos anteriores.

Obtención y establecimiento del valor actual u original de una propiedad individual

En el ejemplo siguiente se muestra cómo se puede leer el valor actual de una propiedad y, a continuación, establecerlo en un nuevo valor:

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";
}

Utilice la propiedad OriginalValue en lugar de la propiedad CurrentValue para leer o establecer el valor original.

Tenga en cuenta que el valor devuelto se escribe como "object" cuando se usa una cadena para especificar el nombre de la propiedad. Por otro lado, el valor devuelto se escribe fuertemente con tipo si se usa una expresión lambda.

Al establecer el valor de la propiedad de esta forma, solo se marcará la propiedad como modificada si el nuevo valor es diferente del valor anterior.

Cuando se establece un valor de propiedad de esta manera, el cambio se detecta automáticamente incluso si AutoDetectChanges está desactivado.

Obtención y establecimiento del valor actual de una propiedad no asignada

También se puede leer el valor actual de una propiedad que no está asignada a la base de datos. Un ejemplo de una propiedad sin asignar podría ser una propiedad RssLink en un blog. Este valor se puede calcular en función de BlogId y, por lo tanto, no es necesario almacenarlo en la base de datos. Por ejemplo:

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;
}

El valor actual también se puede establecer si la propiedad expone un establecedor.

Leer los valores de propiedades no asignadas es útil al realizar la validación de Entity Framework de propiedades no asignadas. Por el mismo motivo, los valores actuales se pueden leer y establecer para las propiedades de las entidades de las que el contexto no realiza el seguimiento actualmente. Por ejemplo:

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";
}

Tenga en cuenta que los valores originales no están disponibles para las propiedades no asignadas o para las propiedades de las entidades de las que el contexto no realiza un seguimiento.

Comprobación de si una propiedad se marca como modificada

En el ejemplo siguiente se muestra cómo comprobar si una propiedad individual está marcada como modificada:

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;
}

Los valores de las propiedades modificadas se envían como actualizaciones a la base de datos cuando se llama a SaveChanges.

Marcar una propiedad como modificada

En el ejemplo siguiente se muestra cómo forzar que una propiedad individual se marque como modificada:

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;
}

Al marcar una propiedad como modificada, se fuerza a enviar una actualización a la base de datos para la propiedad cuando se llama a SaveChanges aunque el valor actual de la propiedad sea el mismo que su valor original.

Actualmente no es posible restablecer una propiedad individual para que no se modifique después de que se haya marcado como modificada. Esto es algo que planeamos admitir en una versión futura.

Lectura de los valores actuales, originales y de base de datos de todas las propiedades de una entidad

En el ejemplo siguiente se muestra cómo leer los valores actuales, los valores originales y los valores efectivamente en la base de datos para todas las propiedades asignadas de una entidad.

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]);
    }
}

Los valores actuales son los valores que contienen actualmente las propiedades de la entidad. Los valores originales son los valores leídos de la base de datos cuando se consultó la entidad. Los valores de base de datos son los valores actualmente almacenados en la base de datos. Obtener los valores de la base de datos es útil cuando los valores de la base de datos pueden haber cambiado desde que se consultó la entidad, como cuando otro usuario ha realizado una edición simultánea a la base de datos.

Establecimiento de valores actuales u originales a partir de otro objeto

Los valores actuales u originales de una entidad con seguimiento se pueden actualizar copiando valores de otro objeto. Por ejemplo:

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; }
}

Al ejecutar el código anterior se imprimirá:

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

Esta técnica se usa a veces al actualizar una entidad con valores obtenidos de una llamada de servicio o un cliente en una aplicación de n niveles. Tenga en cuenta que el objeto usado no tiene que ser del mismo tipo que la entidad, siempre y cuando tenga propiedades cuyos nombres coincidan con los de la entidad. En el ejemplo anterior, se usa una instancia de BlogDTO para actualizar los valores originales.

Tenga en cuenta que solo las propiedades que se establecen en valores diferentes cuando se copian del otro objeto se marcarán como modificadas.

Establecimiento de valores actuales u originales a partir de un diccionario

Los valores actuales u originales de una entidad con seguimiento se pueden actualizar copiando valores de un diccionario o de alguna otra estructura de datos. Por ejemplo:

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);
}

Utilice la propiedad OriginalValues en lugar de la propiedad CurrentValues para establecer los valores originales.

Establecimiento de valores actuales u originales a partir de un diccionario mediante Property

Una alternativa al uso de CurrentValues u OriginalValues como se muestra anteriormente es usar el método Property para establecer el valor de cada propiedad. Esto puede ser preferible cuando necesita establecer los valores de propiedades complejas. Por ejemplo:

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];
    }
}

En el ejemplo anterior, se obtiene acceso a las propiedades complejas mediante nombres de puntos. Para ver otras formas de acceder a propiedades complejas, vea las dos secciones más adelante en este tema que tratan específicamente sobre propiedades complejas.

Creación de un objeto clonado que contenga valores actuales, originales o de base de datos

El objeto DbPropertyValues devuelto por CurrentValues, OriginalValues o GetDatabaseValues se puede usar para crear un clon de la entidad. Este clon contendrá los valores de propiedad del objeto DbPropertyValues usado para crearlo. Por ejemplo:

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

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

Tenga en cuenta que el objeto devuelto no es la entidad y el contexto no realiza el seguimiento. El objeto devuelto tampoco tiene relaciones establecidas con otros objetos.

El objeto clonado puede ser útil para resolver problemas relacionados con las actualizaciones simultáneas de la base de datos, especialmente cuando se usa una interfaz de usuario que implica enlace de datos a objetos de determinado tipo.

Obtención y establecimiento de los valores actuales u originales de propiedades complejas

El valor de un objeto complejo completo se puede leer y establecer mediante el método Property como puede ser para una propiedad primitiva. Además, puede explorar en profundidad el objeto complejo y leer o establecer propiedades de ese objeto, o incluso un objeto anidado. A continuación se muestran algunos ejemplos:

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;
}

Use la propiedad OriginalValue en lugar de la propiedad CurrentValue para obtener o establecer un valor original.

Tenga en cuenta que los métodos Property o ComplexProperty se pueden usar para tener acceso a una propiedad compleja. Sin embargo, el método ComplexProperty debe usarse si desea explorar en profundidad el objeto complejo con llamadas a Property o ComplexProperty adicionales.

Uso de DbPropertyValues para acceder a propiedades complejas

Cuando se usan CurrentValues, OriginalValues o GetDatabaseValues para obtener todos los valores actuales, originales o de base de datos de una entidad, los valores de las propiedades complejas se devuelven como objetos DbPropertyValues anidados. Estos objetos anidados se pueden usar para obtener valores del objeto complejo. Por ejemplo, el método siguiente imprimirá los valores de todas las propiedades, incluidos los valores de las propiedades complejas y las propiedades complejas anidadas.

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]);
        }
    }
}

Para imprimir todos los valores de propiedad actuales, se llamaría al método de la siguiente manera:

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

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