7

dieron una mesa "usuario" y una mesa de "Login" en MS SQL 2008:¿Cómo agrego una columna calculada a mi modelo EF4?

CREATE TABLE [dbo].[User_User](
    [UserID] [int] IDENTITY(1000,1) NOT NULL, 
    [UserName] [varchar](63) NOT NULL, 
    [UserPassword] [varchar](63) NOT NULL 
) 
CREATE TABLE [dbo].[Util_Login](
    [LoginID] [int] IDENTITY(1000,1) NOT NULL, 
    [User_UserID] [int] NOT NULL, -- FK REFERENCES [dbo].[User_User] ([UserID]) 
    [LoginDate] [datetime] NOT NULL, 
) 

¿Cómo ajusto mi User_User entidad modelo de marco de objeto para incluir una columna "UserLastLogin" que devuelve un MAX (LoginDate)?

sé que puedo crear un modelo EF4 alrededor de una vista SQL:

CREATE VIEW [v_User_User] 
AS 
SELECT 
     [User_User].*, 
     (
       SELECT MAX(LoginDate) 
       FROM [Util_Login] 
       WHERE User_UserID = UserID 
     ) AS UserLastLogin 
FROM [User_User] 

Pero hay una manera que sólo puede modificar el modelo User_User para incluir el columnn calculada?

EDIT: Estoy buscando una manera de buscar un usuario o una lista <usuario> incluyendo la fecha Max (Util.LastLogin) en una sola consulta db.

Respuesta

5

Muy buena pregunta, y sí, hay una manera perfecta para lograr esto en EF4:

propiedades personalizadas son una manera de proporcionar propiedades calculadas a entidades. La buena noticia es que las propiedades personalizadas no necesariamente se deben calcular a partir de otras propiedades existentes en la misma entidad, por el código que estamos a punto de ver, ¡se pueden computar desde casi cualquier cosa que nos guste!

Estos son los pasos:
En primer lugar crear una clase parcial y definir una propiedad personalizada en él (Por simplicidad, asumí User_User tabla ha sido asignada a la clase de usuario y Util_Login a Util)

public partial class User { 
    public DateTime LastLoginDate { get; set; } 
} 

por lo tanto, como se puede ver aquí, en lugar de crear una propiedad LastLoginDate en el modelo, que sería necesaria para asignar de nuevo al almacén de datos, hemos creado la propiedad de la clase parcial y luego HÅ ve la opción para poblarlo durante objeto materialización o en la demanda si usted no cree que todos los objetos entidad necesitará proporcionar esa información.

En su caso puede calcular previamente el LastLoginDate propiedad personalizada para cada usuario materializarse es útil ya que creo que se tendrá acceso a este valor para todos (o al menos la mayoría) de las entidades que se materializaron. De lo contrario, debería considerar calcular la propiedad solo cuando sea necesario y no durante la materialización del objeto.

Para eso, vamos a aprovechar ObjectContext.ObjectMaterialized Event, que se genera cada vez que se devuelven datos de una consulta, ya que ObjectContext está creando los objetos de entidad a partir de esos datos. El evento ObjectMaterialized es una cosa de Entity Framework 4. Así que todo lo que tenemos que hacer es crear un controlador de eventos y suscribirlo al evento ObjectMaterialized.

El mejor lugar para poner el código (la suscripción al evento) está dentro de la OnContextCreated Método.Este método es invocado por el constructor del objeto de contexto y las sobrecargas del constructor que es un método parcial sin implementación, simplemente una firma de método creada por el generador de código EF.

Bien, ahora necesitas crear una clase parcial para tu ObjectContext. (Supongo que el nombre es UsersAndLoginsEntities) y suscribo el controlador de eventos (lo llamé Context_ObjectMaterialized) al ObjectMaterialized Event.

public partial class UsersAndLoginsEntities { 
    partial void OnContextCreated() { 
     this.ObjectMaterialized += Context_ObjectMaterialized; 
    } 
} 

El último paso (el verdadero trabajo) sería la implementación de este controlador para realmente poblar la propiedad personalizada para nosotros, que en este caso es muy fácil:

void Context_ObjectMaterialized(object sender, ObjectMaterializedEventArgs args) 
{ 
    if (args.Entity is User) {   
     User user = (User)args.Entity; 
     user.LastLoginDate = this.Utils 
       .Where(u => u.UserID == user.UserID) 
       .Max(u => u.LoginDate); 
    } 
} 


Esperanza esto ayuda.

+0

Gracias por la respuesta detallada! Un problema que veo es que llevará una consulta SQL para establecer el LastLoginDate para cada usuario. p.ej. Si tengo una lista de 50 usuarios, eso significa 1 consulta para completar la lista y 50 consultas adicionales para completar el LastLoginDate para cada usuario. ¿O estoy equivocado sobre el evento ObjectMaterialized? – uhleeka

+1

Eso es correcto, el evento se desencadena para cada objeto de usuario en la lista.Si esto no es deseable, la otra opción sería cargar ansiosamente la propiedad de navegación Util y luego hacer un Max() en ella en el lado del cliente. –

+0

Sí, miré ansiosamente cargar un poco ... ¿no habría una carga ansiosa de que el Util devolviera todas las filas de Utilidades asociadas con el Usuario? Entonces, esencialmente, un lado del cliente Max() estaría tirando todo menos la primera fila. – uhleeka

1

Después de mucha deliberación, que terminó con la siguiente solución:

En primer lugar, crear una vista que contiene todos los campos de los usuarios, además de un campo de fecha lastlogin (de mi post original).

Después de añadir el usuario (llamarlo User_Model) y la vista del usuario (llamarlo UserView_Model) a mi modelo de EF, creé una clase contenedora (llamarlo User_Wrapper) alrededor de la User_Model y añadió un adicional de Propiedad DateTime para LastLogin.

Modifiqué la clase User_Wrapper para recuperarla del UserView_Model, y luego rellené el User_Model subyacente al reflejar todas las propiedades compartidas entre User_Model y UserView_Model. Finalmente, establecí la propiedad User_Wrapper.LastLogin en función de la vista de usuario recuperada.

Todas las demás funciones (Crear, Actualizar, Eliminar ...) operan en el User_Model. Solo el Fetch usa el UserView_Model.


¿Qué hizo todo esto? Ahora solo tengo una llamada a la base de datos para rellenar una sola User_Wrapper o una lista <User_Wrapper>.

¿Los inconvenientes? Supongo que porque mi UserView_Model no tiene ninguna relación asociada, no podría hacer ninguna carga ansiosa usando el EF ObjectContext. Afortunadamente, en mi situación, no creo que eso sea un problema.

¿Hay una manera mejor?

1

Acabo de tener una situación en la que necesitaba contar propiedades para dos entidades relacionadas sin cargar las colecciones. Una cosa que descubrí es que necesita tener MultipleActiveResultSets = True en la cadena de conexión para evitar que se genere una excepción en el controlador de eventos ObjectMaterialized al consultar otras colecciones de entidades.

Cuestiones relacionadas