Pregunta Qué son buenas prácticas de diseño cuando se trabaja con Entity Framework


Esto se aplicará principalmente para una aplicación asp.net donde no se puede acceder a los datos a través de soa. Lo que significa que tiene acceso a los objetos cargados desde el marco, no Transferir Objetos, aunque todavía se aplican algunas recomendaciones.

Esta es una publicación de la comunidad, así que agréguela como mejor le parezca.

Se aplica a: Entity Framework 1.0 incluido con Visual Studio 2008 sp1.

¿Por qué elegir EF en primer lugar?

Teniendo en cuenta que es una tecnología joven con muchos problemas (ver abajo), puede ser difícil vender el carro de EF para su proyecto. Sin embargo, es la tecnología que impulsa Microsoft (a expensas de Linq2Sql, que es un subconjunto de EF). Además, puede que no esté satisfecho con NHibernate u otras soluciones que existen. Sean cuales sean las razones, hay personas (incluyéndome a mí) que trabajan con EF y la vida no está mal. Te hacen pensar.

EF y herencia

El primer gran tema es la herencia. EF admite mapeo para clases heredadas que persisten de 2 maneras: tabla por clase y tabla la jerarquía. El modelado es fácil y no hay problemas de programación con esa parte.

(Lo siguiente se aplica a la tabla por modelo de clase, ya que no tengo experiencia con tabla por jerarquía, que, de todos modos, es limitada.) El verdadero problema surge cuando intenta ejecutar consultas que incluyen uno o varios objetos que son parte de un árbol de herencia: el sql generado es increíblemente horrible, toma mucho tiempo ser analizado por el EF y lleva mucho tiempo ejecutarlo también. Este es un verdadero obstáculo del espectáculo. Suficiente que EF no debería usarse con herencia o lo menos posible.

Aquí hay un ejemplo de lo malo que fue. Mi modelo de EF tenía ~ 30 clases, ~ 10 de las cuales eran parte de un árbol de herencia. Al ejecutar una consulta para obtener un elemento de la clase Base, algo tan simple como Base.Get (id), el SQL generado tenía más de 50,000 caracteres. Luego, cuando intenta devolver algunas Asociaciones, degenera aún más, yendo tan lejos como lanzando excepciones de SQL sobre no poder consultar más de 256 tablas a la vez.

Bien, esto es malo, el concepto EF es para permitirle crear su estructura de objeto sin (o con la menor cantidad posible) consideración sobre la implementación real de la base de datos de su tabla. Falla completamente en esto.

Entonces, ¿recomendaciones? Evite la herencia si puede, el rendimiento será mucho mejor. Úselo con moderación donde sea necesario. En mi opinión, esto convierte a EF en una herramienta glorificada de generación de sql para consultas, pero aún existen ventajas al usarla. Y formas de implementar mecanismos que son similares a la herencia.

Pasar por alto la herencia con interfaces

Lo primero que debe saberse al intentar obtener algún tipo de herencia con EF es que no puede asignar a una clase no modelada por EF una clase base. Ni siquiera lo intentes, el modelador lo sobreescribirá. ¿Entonces lo que hay que hacer?

Puede usar interfaces para hacer que las clases implementen alguna funcionalidad. Por ejemplo, aquí hay una interfaz IEntity que le permite definir asociaciones entre entidades EF en las que no sabe en tiempo de diseño cuál sería el tipo de entidad.

public enum EntityTypes{ Unknown = -1, Dog = 0, Cat }
public interface IEntity
{
    int EntityID { get; }
    string Name { get; }
    Type EntityType { get; }
}
public partial class Dog : IEntity
{
   // implement EntityID and Name which could actually be fields 
   // from your EF model
   Type EntityType{ get{ return EntityTypes.Dog; } }
}

Al usar este IEntity, puedes trabajar con asociaciones indefinidas en otras clases

// lets take a class that you defined in your model.
// that class has a mapping to the columns: PetID, PetType
public partial class Person
{
    public IEntity GetPet()
    {
        return IEntityController.Get(PetID,PetType);
    }
}

que hace uso de algunas funciones de extensión:

public class IEntityController
{
    static public IEntity Get(int id, EntityTypes type)
    {
        switch (type)
        {
            case EntityTypes.Dog: return Dog.Get(id);
            case EntityTypes.Cat: return Cat.Get(id);
            default: throw new Exception("Invalid EntityType");
        }
    }
}

No es tan limpio como tener una herencia simple, particularmente teniendo en cuenta que tiene que almacenar el PetType en un campo de base de datos adicional, pero teniendo en cuenta las ganancias de rendimiento, no miraría hacia atrás.

Tampoco puede modelar una relación uno a muchos, muchos a muchos, pero con usos creativos de 'Unión' podría funcionar. Finalmente, crea el efecto lateral de cargar datos en una propiedad / función del objeto, que debe tener cuidado. Usar una convención de nomenclatura clara como GetXYZ () ayuda a ese respecto.

Consultas compiladas

El rendimiento de Entity Framework no es tan bueno como el acceso directo a la base de datos con ADO (obviamente) o Linq2SQL. Sin embargo, hay formas de mejorarlo, uno de los cuales es compilar sus consultas. El rendimiento de una consulta compilada es similar a Linq2Sql.

¿Qué es una consulta compilada? Es simplemente una consulta para la cual le dice al marco que mantenga el árbol analizado en la memoria para que no sea necesario regenerarlo la próxima vez que lo ejecute. Entonces, en la próxima ejecución, ahorrará el tiempo que lleva analizar el árbol. No descarte eso ya que es una operación muy costosa que empeora con consultas más complejas.

Hay dos formas de compilar una consulta: crear un ObjectQuery con EntitySQL y usar la función CompiledQuery.Compile (). (Tenga en cuenta que al usar un EntityDataSource en su página, de hecho estará usando ObjectQuery con EntitySQL, de modo que se compilará y almacenará en caché).

Un lado aquí en caso de que no sepas qué es EntitySQL. Es una forma basada en cadenas de escribir consultas contra el EF. Aquí hay un ejemplo: "seleccione el valor del perro de Entities.DogSet como dog donde dog.ID = @ID". La sintaxis es bastante similar a la sintaxis SQL. También puede hacer una manipulación de objetos bastante compleja, que está bien explicada [aquí] [1].

Ok, entonces aquí está cómo hacerlo usando ObjectQuery <>

        string query = "select value dog " +
                       "from Entities.DogSet as dog " +
                       "where dog.ID = @ID";

        ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance));
        oQuery.Parameters.Add(new ObjectParameter("ID", id));
        oQuery.EnablePlanCaching = true;
        return oQuery.FirstOrDefault();

La primera vez que ejecuta esta consulta, el marco generará el árbol de expresiones y lo mantendrá en la memoria. Entonces, la próxima vez que se ejecute, ahorrará en ese costoso paso. En ese ejemplo EnablePlanCaching = true, que es innecesario ya que esa es la opción predeterminada.

La otra forma de compilar una consulta para su uso posterior es el método CompiledQuery.Compile. Esto usa un delegado:

    static readonly Func<Entities, int, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
            ctx.DogSet.FirstOrDefault(it => it.ID == id));

o usando linq

    static readonly Func<Entities, int, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
            (from dog in ctx.DogSet where dog.ID == id select dog).FirstOrDefault());

para llamar a la consulta:

query_GetDog.Invoke( YourContext, id );

La ventaja de CompiledQuery es que la sintaxis de su consulta se comprueba en tiempo de compilación, donde no lo es como EntitySQL. Sin embargo, hay otra consideración ...

Incluye

Supongamos que desea que la consulta envíe los datos del propietario del perro para evitar hacer 2 llamadas a la base de datos. Fácil de hacer, ¿verdad?

EntitySQL

        string query = "select value dog " +
                       "from Entities.DogSet as dog " +
                       "where dog.ID = @ID";
        ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)).Include("Owner");
        oQuery.Parameters.Add(new ObjectParameter("ID", id));
        oQuery.EnablePlanCaching = true;
        return oQuery.FirstOrDefault();

CompiledQuery

    static readonly Func<Entities, int, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
            (from dog in ctx.DogSet.Include("Owner") where dog.ID == id select dog).FirstOrDefault());

Ahora, ¿y si quieres tener el parámetro Include? Lo que quiero decir es que quieres tener una única función Get () llamada desde diferentes páginas que se preocupen por las diferentes relaciones del perro. Uno se preocupa por el Propietario, otro por su Comida Favorita, otro por su FavotireToy y demás. Básicamente, desea decirle a la consulta qué asociaciones cargar.

Es fácil de hacer con EntitySQL

public Dog Get(int id, string include)
{
        string query = "select value dog " +
                       "from Entities.DogSet as dog " +
                       "where dog.ID = @ID";

        ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance))
    .IncludeMany(include);
        oQuery.Parameters.Add(new ObjectParameter("ID", id));
        oQuery.EnablePlanCaching = true;
        return oQuery.FirstOrDefault();
}

El include simplemente usa la cadena pasada. Suficientemente fácil. Tenga en cuenta que es posible mejorar la función Incluir (cadena) (que acepta solo una ruta única) con un IncludeMany (cadena) que le permitirá pasar una cadena de asociaciones separadas por comas para cargar. Busque más en la sección de extensión para esta función.

Sin embargo, si intentamos hacerlo con CompiledQuery, nos encontramos con numerosos problemas:

Lo obvio

    static readonly Func<Entities, int, string, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) =>
            (from dog in ctx.DogSet.Include(include) where dog.ID == id select dog).FirstOrDefault());

se ahogará cuando se lo llame con:

query_GetDog.Invoke( YourContext, id, "Owner,FavoriteFood" );

Porque, como se mencionó anteriormente, Include () solo quiere ver una sola ruta en la cadena y aquí le damos 2: "Owner" y "FavoriteFood" (que no debe confundirse con "Owner.FavoriteFood"!).

Entonces, usemos IncludeMany (), que es una función de extensión

    static readonly Func<Entities, int, string, Dog> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) =>
            (from dog in ctx.DogSet.IncludeMany(include) where dog.ID == id select dog).FirstOrDefault());

De nuevo, esta vez es incorrecto porque EF no puede analizar IncludeMany porque no es parte de las funciones que reconoce: es una extensión.

De acuerdo, entonces quiere pasar un número arbitrario de rutas a su función e Includes () solo toma una sola. ¿Qué hacer? Puede decidir que nunca necesitará más de, digamos 20 Includes, y pasar cada cadena separada en una estructura a CompiledQuery. Pero ahora la consulta se ve así:

from dog in ctx.DogSet.Include(include1).Include(include2).Include(include3)
.Include(include4).Include(include5).Include(include6)
.[...].Include(include19).Include(include20) where dog.ID == id select dog

lo cual es horrible también. Ok, entonces, pero espera un minuto. ¿No podemos devolver un ObjectQuery <> con CompiledQuery? ¿Entonces configura los includes en eso? Bueno, eso también lo habría pensado así:

    static readonly Func<Entities, int, ObjectQuery<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, ObjectQuery<Dog>>((ctx, id) =>
            (ObjectQuery<Dog>)(from dog in ctx.DogSet where dog.ID == id select dog));
public Dog GetDog( int id, string include )
{
    ObjectQuery<Dog> oQuery = query_GetDog(id);
    oQuery = oQuery.IncludeMany(include);
    return oQuery.FirstOrDefault;   
}

Eso debería haber funcionado, excepto que cuando llamas a IncludeMany (o Include, Where, OrderBy ...) invalidas la consulta compilada en caché porque ahora es completamente nueva. Entonces, el árbol de expresiones necesita ser reparado y obtienes ese rendimiento nuevamente.

¿Entonces, cuál es la solución? Simplemente no puede usar CompiledQueries con Includes parametrized. Use EntitySQL en su lugar. Esto no significa que no hay usos para CompiledQueries. Es ideal para consultas localizadas que siempre serán llamadas en el mismo contexto. Lo ideal es que siempre se use CompiledQuery porque la sintaxis se comprueba en tiempo de compilación, pero debido a la limitación, eso no es posible.

Un ejemplo de uso sería: es posible que desee tener una página que pregunte qué dos perros tienen el mismo alimento favorito, que es un poco estrecho para una función BusinessLayer, por lo que lo coloca en su página y sabe exactamente qué tipo de incluye son necesario.

Pasar más de 3 parámetros a CompiledQuery

Func está limitado a 5 parámetros, de los cuales el último es el tipo de devolución y el primero es su objeto Entidades del modelo. Entonces eso te deja con 3 parámetros. Una lástima, pero se puede mejorar muy fácilmente.

public struct MyParams
{
    public string param1;
    public int param2;
    public DateTime param3;
}

    static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((ctx, myParams) =>
            from dog in ctx.DogSet where dog.Age == myParams.param2 && dog.Name == myParams.param1 and dog.BirthDate > myParams.param3 select dog);

public List<Dog> GetSomeDogs( int age, string Name, DateTime birthDate )
{
    MyParams myParams = new MyParams();
    myParams.param1 = name;
    myParams.param2 = age;
    myParams.param3 = birthDate;
    return query_GetDog(YourContext,myParams).ToList();
}

Tipos de devolución (esto no se aplica a las consultas de EntitySQL ya que no se compilan al mismo tiempo durante la ejecución como el método CompiledQuery)

Al trabajar con Linq, por lo general no se fuerza la ejecución de la consulta hasta el último momento, en caso de que algunas otras funciones en sentido descendente quieran cambiar la consulta de alguna forma:

    static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) =>
            from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog);

public IEnumerable<Dog> GetSomeDogs( int age, string name )
{
    return query_GetDog(YourContext,age,name);
}
public void DataBindStuff()
{
    IEnumerable<Dog> dogs = GetSomeDogs(4,"Bud");
    // but I want the dogs ordered by BirthDate
    gridView.DataSource = dogs.OrderBy( it => it.BirthDate );

}

¿Qué va a pasar aquí? Al jugar con ObjectQuery original (que es el tipo de devolución real de la instrucción Linq, que implementa IEnumerable), invalidará la consulta compilada y será forzada a volver a analizar. Entonces, la regla de oro es devolver una Lista <> de objetos en su lugar.

    static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) =>
            from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog);

public List<Dog> GetSomeDogs( int age, string name )
{
    return query_GetDog(YourContext,age,name).ToList(); //<== change here
}
public void DataBindStuff()
{
    List<Dog> dogs = GetSomeDogs(4,"Bud");
    // but I want the dogs ordered by BirthDate
    gridView.DataSource = dogs.OrderBy( it => it.BirthDate );

}

Cuando llama a ToList (), la consulta se ejecuta según la consulta compilada y luego, más tarde, OrderBy se ejecuta contra los objetos en la memoria. Puede ser un poco más lento, pero ni siquiera estoy seguro. Una cosa segura es que no tiene preocupaciones sobre el manejo incorrecto de ObjectQuery y la invalidación del plan de consulta compilado.

Una vez más, esa no es una declaración general. ToList () es un truco de programación defensiva, pero si tienes una razón válida para no usar ToList (), adelante. Hay muchos casos en los que le gustaría refinar la consulta antes de ejecutarla.

Actuación

¿Cuál es el impacto en el rendimiento de compilar una consulta? En realidad, puede ser bastante grande. Una regla general es que compilar y almacenar en caché la consulta para su reutilización lleva al menos el doble de tiempo que simplemente ejecutarlo sin almacenar en caché. Para consultas complejas (leer inherirante), he visto hasta 10 segundos.

Por lo tanto, la primera vez que se llama a una consulta precompilada, obtiene un golpe de rendimiento. Después de ese primer golpe, el rendimiento es notablemente mejor que la misma consulta no precompilada. Prácticamente lo mismo que Linq2Sql

Cuando carga una página con consultas precompiladas la primera vez, recibirá un golpe. Se cargará en tal vez 5-15 segundos (obviamente, más de una consulta precompilada terminará siendo llamada), mientras que las cargas posteriores tomarán menos de 300ms. Dramática diferencia, y depende de usted decidir si está bien que su primer usuario dé un golpe o si quiere que un script llame a sus páginas para forzar una compilación de las consultas.

¿Puede esta consulta ser almacenada en caché?

{
    Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog;
}

No, las consultas Linq ad-hoc no se almacenan en caché y usted incurrirá en el costo de generar el árbol cada vez que lo llame.

Consultas parametrizadas

La mayoría de las capacidades de búsqueda involucran consultas fuertemente parametrizadas. Incluso hay bibliotecas disponibles que le permitirán construir una consulta parametrizada a partir de expresiones lamba. El problema es que no puedes usar consultas precompiladas con eso. Una forma de evitarlo es trazar todos los criterios posibles en la consulta y marcar cuál de ellos desea usar:

public struct MyParams
{
    public string name;
public bool checkName;
    public int age;
public bool checkAge;
}

    static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog =
        CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((ctx, myParams) =>
            from dog in ctx.DogSet 
    where (myParams.checkAge == true && dog.Age == myParams.age) 
        && (myParams.checkName == true && dog.Name == myParams.name ) 
    select dog);

protected List<Dog> GetSomeDogs()
{
    MyParams myParams = new MyParams();
    myParams.name = "Bud";
    myParams.checkName = true;
    myParams.age = 0;
    myParams.checkAge = false;
    return query_GetDog(YourContext,myParams).ToList();
}

La ventaja aquí es que obtienes todos los beneficios de una consulta precompilada. Las desventajas son que lo más probable es que termines con una cláusula WHERE que es bastante difícil de mantener, que incurrirá en una penalización mayor por precompilar la consulta y que cada consulta que ejecutes no es tan eficiente como podría ser (particularmente con combinaciones lanzadas).

Otra forma es construir una consulta EntitySQL pieza por pieza, como todos hicimos con SQL.

protected List<Dod> GetSomeDogs( string name, int age)
{
string query = "select value dog from Entities.DogSet where 1 = 1 ";
    if( !String.IsNullOrEmpty(name) )
        query = query + " and dog.Name == @Name ";
if( age > 0 )
    query = query + " and dog.Age == @Age ";

    ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>( query, YourContext );
    if( !String.IsNullOrEmpty(name) )
        oQuery.Parameters.Add( new ObjectParameter( "Name", name ) );
if( age > 0 )
        oQuery.Parameters.Add( new ObjectParameter( "Age", age ) );

return oQuery.ToList();
}

Aquí los problemas son:  - no hay comprobación de sintaxis durante la compilación  - cada combinación diferente de parámetros genera una consulta diferente que deberá precompilarse cuando se ejecute por primera vez. En este caso, solo hay 4 consultas posibles diferentes (sin params, solo por edad, solo nombre y ambos params), pero se puede ver que puede haber mucho más con una búsqueda mundial normal.  - ¡A nadie le gusta concatenar cadenas!

Otra opción es consultar un gran subconjunto de datos y luego reducirlo en la memoria. Esto es particularmente útil si está trabajando con un subconjunto definido de los datos, como todos los perros en una ciudad. Sabes que hay muchas pero también sabes que no hay tantas ... así que tu página de búsqueda de CityDog puede cargar todos los perros de la ciudad en memoria, que es una única consulta precompilada y luego refinar los resultados

protected List<Dod> GetSomeDogs( string name, int age, string city)
{
string query = "select value dog from Entities.DogSet where dog.Owner.Address.City == @City ";
    ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>( query, YourContext );
    oQuery.Parameters.Add( new ObjectParameter( "City", city ) );

List<Dog> dogs = oQuery.ToList();

if( !String.IsNullOrEmpty(name) )
        dogs = dogs.Where( it => it.Name == name );
if( age > 0 )
        dogs = dogs.Where( it => it.Age == age );

return dogs;
}

Es particularmente útil cuando comienza a mostrar todos los datos y luego permite el filtrado.

Problemas:  - Podría llevar a una transferencia de datos grave si no tiene cuidado con su subconjunto.  - Solo puedes filtrar los datos que devolviste. Significa que si no devuelve la asociación Dog.Owner, no podrá filtrar en Dog.Owner.Name Entonces, ¿cuál es la mejor solución? No hay ninguno. Debe elegir la solución que mejor se adapte a usted y a su problema: - Utilice el desarrollo de consultas basado en lambda cuando no le importe precompilar sus consultas. - Utilice una consulta Linq precompilada completamente definida cuando su estructura de objetos no sea demasiado compleja. - Usar la concatenación EntitySQL / cadena cuando la estructura puede ser compleja y cuando el número posible de consultas resultantes diferentes es pequeño (lo que significa menos hits de precompilación). - Utilice el filtrado en memoria cuando trabaje con un subconjunto pequeño de datos o cuando tenga que buscar todos los datos en los datos al principio (si el rendimiento es bueno con todos los datos, entonces el filtrado en la memoria no causar cualquier tiempo para ser gastado en el db).

Acceso Singleton

La mejor manera de tratar con su contexto y entidades a través de todas sus páginas es usar el patrón singleton:

public sealed class YourContext
{
    private const string instanceKey = "On3GoModelKey";

    YourContext(){}

    public static YourEntities Instance
    {
        get
        {
            HttpContext context = HttpContext.Current;
            if( context == null )
                return Nested.instance;

            if (context.Items[instanceKey] == null)
            {
                On3GoEntities entity = new On3GoEntities();
                context.Items[instanceKey] = entity;
            }
            return (YourEntities)context.Items[instanceKey];
        }
    }

    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly YourEntities instance = new YourEntities();
    }
}

NoTracking, ¿vale la pena?

Al ejecutar una consulta, puede decirle al marco que rastree los objetos que devolverá o no. Qué significa eso? Con el seguimiento habilitado (la opción predeterminada), el marco de trabajo rastreará lo que está sucediendo con el objeto (¿ha sido modificado? ¿Creado? ¿Eliminado?) Y también vinculará objetos, cuando se realicen más consultas desde la base de datos, que es lo que es de interés aquí.

Por ejemplo, supongamos que Dog con ID == 2 tiene un propietario con ID == 10.

Dog dog = (from dog in YourContext.DogSet where dog.ID == 2 select dog).FirstOrDefault();
    //dog.OwnerReference.IsLoaded == false;
    Person owner = (from o in YourContext.PersonSet where o.ID == 10 select dog).FirstOrDefault();
    //dog.OwnerReference.IsLoaded == true;

Si tuviéramos que hacer lo mismo sin seguimiento, el resultado sería diferente.

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)
    (from dog in YourContext.DogSet where dog.ID == 2 select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
Dog dog = oDogQuery.FirstOrDefault();
    //dog.OwnerReference.IsLoaded == false;
ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)
    (from o in YourContext.PersonSet where o.ID == 10 select o);
oPersonQuery.MergeOption = MergeOption.NoTracking;
    Owner owner = oPersonQuery.FirstOrDefault();
    //dog.OwnerReference.IsLoaded == false;

El seguimiento es muy útil y en un mundo perfecto sin problemas de rendimiento, siempre estaría activo. Pero en este mundo, hay un precio para él, en términos de rendimiento. Entonces, ¿deberías usar NoTracking para acelerar las cosas? Depende de lo que planea usar los datos para.

¿Hay alguna posibilidad de que los datos de su consulta con NoTracking se puedan utilizar para actualizar / insertar / eliminar en la base de datos? Si es así, no use NoTracking porque las asociaciones no son rastreadas y provocarán excepciones.

En una página donde no hay absolutamente ninguna actualización de la base de datos, puede usar NoTracking.

El seguimiento de mezcla y NoTracking es posible, pero requiere que tenga mucho cuidado con las actualizaciones / inserciones / eliminaciones. El problema es que si se mezcla, corre el riesgo de que el marco intente Adjuntar () un objeto NoTracking al contexto donde existe otra copia del mismo objeto con el seguimiento activado. Básicamente, lo que estoy diciendo es que

Dog dog1 = (from dog in YourContext.DogSet where dog.ID == 2).FirstOrDefault();

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)
    (from dog in YourContext.DogSet where dog.ID == 2 select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
Dog dog2 = oDogQuery.FirstOrDefault();

dog1 y dog2 son 2 objetos diferentes, uno rastreado y otro no. Usar el objeto separado en una actualización / inserción forzará un Adjuntar () que dirá "Espera un minuto, ya tengo un objeto aquí con la misma clave de base de datos. Error". Y cuando Adjuntas () un objeto, toda su jerarquía se adjunta también, causando problemas en todas partes. Ten mucho cuidado.

Cuanto más rápido es con NoTracking

Depende de las consultas. Algunos son mucho más susceptibles de rastrear que otros. No tengo una regla fácil y rápida para eso, pero ayuda.

Entonces, ¿debería usar NoTracking en todas partes?

No exactamente. Hay algunas ventajas para el seguimiento de objetos. El primero es que el objeto está en la memoria caché, por lo que la llamada subsiguiente para ese objeto no golpeará la base de datos. Esa memoria caché solo es válida durante el tiempo de vida del objeto YourEntities, que, si usa el código singleton anterior, es igual a la duración de la página. Solicitud de una página == un objeto YourEntity. Por lo tanto, para múltiples llamadas para el mismo objeto, solo se cargará una vez por solicitud de página. (Otro mecanismo de almacenamiento en caché podría extender eso).

¿Qué sucede cuando está utilizando NoTracking e intenta cargar el mismo objeto varias veces? La base de datos se consultará cada vez, por lo que hay un impacto allí. ¿Con qué frecuencia debe / debe llamar para el mismo objeto durante una solicitud de una sola página? Lo menos posible, por supuesto, pero sucede.

¿También recuerdas la pieza anterior sobre tener las asociaciones conectadas automáticamente para ti? No tiene eso con NoTracking, por lo que si carga sus datos en varios lotes, no tendrá un enlace entre ellos:

ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)(from dog in YourContext.DogSet select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
List<Dog> dogs = oDogQuery.ToList();

ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)(from o in YourContext.PersonSet  select o);
oPersonQuery.MergeOption = MergeOption.NoTracking;
    List<Person> owners = oPersonQuery.ToList();

En este caso, ningún perro tendrá su propiedad .Owner establecida.

Algunas cosas a tener en cuenta cuando intenta optimizar el rendimiento.

Sin cargas flojas, ¿qué debo hacer?

Esto puede verse como una bendición disfrazada. Por supuesto, es molesto cargar todo manualmente. Sin embargo, disminuye el número de llamadas a la base de datos y te obliga a pensar cuándo deberías cargar datos. Cuanto más puedas cargar en una llamada a la base de datos, mejor. Eso siempre fue cierto, pero se aplica ahora con esta 'característica' de EF.

Por supuesto, puedes llamar     if (! ObjectReference.IsLoaded) ObjectReference.Load (); si lo desea, pero una mejor práctica es forzar al marco a cargar los objetos que sabe que necesitará en una sola toma. Aquí es donde la discusión sobre las inclusiones parametrizadas comienza a tener sentido.

Digamos que tienes tu objeto Dog

public class Dog
{
    public Dog Get(int id)
    {
        return YourContext.DogSet.FirstOrDefault(it => it.ID == id );
    }
}

Este es el tipo de función con la que trabaja todo el tiempo. Se llama desde todo el lugar y una vez que tienes ese objeto Dog, le harás cosas muy diferentes en diferentes funciones. En primer lugar, debe precompilarse, porque lo llamará con mucha frecuencia. En segundo lugar, cada página diferente querrá tener acceso a un subconjunto diferente de los datos del Perro. Algunos querrán al Dueño, otros al FavoriteToy, etc.

Por supuesto, puede llamar a Load () por cada referencia que necesite en cualquier momento que lo necesite. Pero eso generará una llamada a la base de datos cada vez. Mala idea. Entonces, en cambio, cada página solicitará los datos que quiere ver cuando solicite por primera vez el objeto Dog:

    static public Dog Get(int id) { return GetDog(entity,"");}
    static public Dog Get(int id, string includePath)
{
        string query = "select value o " +
            " from YourEntities.DogSet as o " +

74


origen


Respuestas:


No utilice toda la información anterior, como "Acceso único". Es absolutamente 100% no debe almacenar este contexto para ser reutilizado ya que no es seguro para subprocesos.


3



Si bien es informativo, creo que puede ser más útil compartir cómo encaja todo esto en una arquitectura de solución completa. Ejemplo: obtuve una solución que muestra dónde usa tanto herencia de EF como su alternativa para que muestre su diferencia de rendimiento.


1