2009-02-13 18 views
74

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.¿Cuáles son las buenas prácticas de diseño cuando se trabaja con Entity Framework

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

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

¿Por qué elegir EF en primer lugar?

Considerando que es una tecnología joven con muchos problemas (ver a continuación), 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 la 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 es, de todos modos, 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.

Ok, 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.

Sin pasar por la herencia con los interfaces

primero que debe saber con tratar de obtener algún tipo de herencia que va con EF es que no se puede asignar una clase de modelado-no-EF una clase base. Ni siquiera lo intentes, el modelador lo sobreescribirá. ¿Entonces lo que hay que hacer?

Puede usar interfaces para exigir 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; } } 
} 

El uso de este IEntity, a continuación, puede trabajar con las asociaciones definidas 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 teniendo la herencia llanura , sobre todo teniendo en cuenta que tiene que almacenar PetType en un campo de base de datos adicional, pero teniendo en cuenta el aumento del rendimiento, no miraría hacia atrás.

Tampoco puede modelar la 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.

Compilado consultas

rendimiento marco de la entidad no es tan buena como la base de datos de acceso directo 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é).

Aparte 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, así que aquí es 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 se ejecuta esta consulta, el marco será generar el árbol de expresión y la guardan 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.Este sistema utiliza 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 el uso de 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 la consulta se comprueba en tiempo de compilación , donde como EntitySQL no es. Sin embargo, hay otra consideración ...

Incluye

Digamos que usted quiere tener los datos para el dueño del perro para ser devueltos por la consulta 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, lo que si desea tener la opción Incluir parametrizar? 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 incluyen 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.

Si tratamos de hacerlo con CompiledQuery sin embargo, nos encontramos con numerosos problemas:

La obvia

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 ahogue cuando se le llama con:

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

Porque, como lo mencionan arriba, 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"!).

A continuación, vamos a utilizar 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()); 

Mal de nuevo, esta vez es debido a que la EF no puede analizar IncludeMany porque no es parte de las funciones que se reconoce: se es una extensión

Bien, 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 

que es horrible también. Ok, entonces, pero espera un minuto. ¿No podemos devolver un ObjectQuery <> con CompiledQuery? ¿Entonces configura los includes en eso? Bueno, lo que yo hubiera pensado lo mismo:

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

que debería haber funcionado, excepto que cuando se llama a IncludeMany (o incluir, cuando, OrdenarPor ...) que invalida la consulta compilada en caché, ya que es ¡uno completamente nuevo ahora! Entonces, el árbol de expresiones necesita ser reparado y obtienes ese rendimiento nuevamente.

¿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, así que póngalo en su página y sepa exactamente qué tipo de incluye son obligatorios

Pasando más de 3 parámetros para un CompiledQuery

Func se limita a 5 parámetros, de los cuales el último es el tipo de retorno y la primera es su Entidades objeto 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 regreso (esto no se aplica a EntitySQL consultas, ya que no se compilan al mismo tiempo durante la ejecución como el método CompiledQuery)

Trabajar con LINQ, por lo general no forzar la ejecución de la consulta hasta el último momento, en caso otras funciones aguas abajo quiere cambiar la consulta de alguna manera:

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

} 

lo que 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 se llama a ToList(), la consulta se ejecuta de acuerdo con la consulta compilada y luego, más tarde, el OrdenarPor se ejecuta en contra de los objetos en 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.

Rendimiento

¿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 que recibe 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.

¿Se puede almacenar en caché esta consulta?

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

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

parametrizada Consultas

La mayoría de las capacidades de búsqueda implican en gran medida las consultas 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 evitar que se va a trazar todos los posibles criterios en la consulta y la bandera que se desea utilizar:

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 usted obtiene todos los benifits de un Quert precompilado. 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 crear 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 ninguna comprobación de sintaxis durante la compilación - cada combinación diferente de parámetros generar una consulta diferente que se necesita para ser pre-compilados cuando se ejecuta 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 conducir a una transferencia de datos grave si no tiene cuidado con su subconjunto. - Solo puede filtrar los datos que haya devuelto.Significa que si no devuelve la asociación Dog.Owner, no podrá filtrar en Dog.Owner.Name ¿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 la estructura de su objeto no sea demasiado compleja. - Utilice EntitySQL/cadena de concatenación cuando la estructura podría ser compleja y cuando el número posible de consultas diferentes resultantes sea pequeña (lo que significa menos visitas de precompilación). - Utilice el filtrado en memoria cuando trabaje con un subconjunto pequeño de los 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 causa ningún tiempo para ser gastado en el DB).

Singleton acceso

La mejor manera de hacer frente a su contexto y entidades cruzando todas sus páginas es utilizar 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, ¿merece la pena?

Al ejecutar una consulta, puede decirle al marco que haga un seguimiento de 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 el perro con ID == 2 tiene un propietario, que == 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 el 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 hacer la actualización/inserción/eliminación 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 utilizar NoTracking.

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.

¿Cuánto 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.

¿Recuerdas también la pieza anterior acerca de tener las asociaciones conectadas automáticamente para ti? Usted no tiene que con NoTracking, por lo que si se cargan los datos en varios lotes, usted no tendrá un enlace a 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 conjunto de propiedades .owner.

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

No hay carga lenta, ¿qué debo hacer?

Esto se puede ver 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, puede llamar al if (! ObjectReference.IsLoaded) ObjectReference.Load(); si lo desea, pero una práctica mejor 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 usted tiene objeto perro

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

Este es el tipo de función que trabaja con 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 el propietario, otros el FavoriteToy, etc.

Por supuesto, puede llamar a Load() para cada referencia que necesite cada vez que la necesite. Pero eso generará una llamada a la base de datos cada vez. Mala idea.Así que en lugar, cada página le pedirá los datos que quiere ver cuando la primera solicitud para el objeto del perro:

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 " + 
+15

En serio, ¿no tienes un blog o sitio web propio donde podrías haber publicado este ensayo? – AnthonyWJones

+7

@AnthonyWJones, bueno, aunque no es la publicación habitual en SO; Si escuchas el podcast de SO, Jeff siempre usa el mantra de que SO es un lugar para que los programadores compartan conocimientos que no tienen blogs. Esto NO es ofensivo Y basado en el mantra de Jeff, no debería ser cerrado en mi opinión. – BobbyShaftoe

+0

Solo para agregar, creo que también es interesante que esta publicación probablemente se votara varias veces si fuera una respuesta a la pregunta; la pregunta podría ser simplemente el título; sin embargo, la gente lo está tildando de ofensivo ... extraño. – BobbyShaftoe

Respuesta

1

Mientras informativo creo que puede ser más útil para compartir cómo todo esto encaja en una arquitectura de la 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.

3

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

+0

+1 sin duda está en contra del enfoque de la unidad de trabajo. No es compatible con la cancelación de ediciones y, al guardar el contexto, tendrá problemas para saber qué se cambió realmente desde la última vez que se guardó. – surfen

Cuestiones relacionadas