2010-07-18 20 views
38

Al llamar a Max() en un IQueryable y no hay registros, obtengo la siguiente excepción.Entity Framework llamando a MAX en nulo en registros

La conversión al tipo de valor 'Int32' ha fallado porque el valor materializado es nulo. El parámetro genérico del tipo de resultado o la consulta deben usar un tipo que admite nulos.

var version = ctx.Entries 
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId) 
    .Max(e => e.Version); 

Ahora entiendo por qué sucede esto, mi pregunta es ¿cómo es la mejor manera de hacer esto si la tabla puede estar vacío. El siguiente código funciona y resuelve este problema, pero es muy feo ¿no existe el concepto MaxOrDefault()?

int? version = ctx.Entries 
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId) 
    .Select(e => (int?)e.Version) 
    .Max(); 
+0

¿Qué versión de EF está usted usando ? – jrista

+0

Microsoft ADO.NET Entity Framework Feature Community Technology Preview 4 – bleevo

Respuesta

68

Sí, echando a anulables de T es la forma recomendada para tratar el problema en LINQ a Entidades consultas. Tener un método MaxOrDefault() que tenga la firma correcta suena como una idea interesante, pero simplemente necesitaría una versión adicional para cada método que presente este problema, que no se escalaría muy bien.

Este es uno de los muchos desajustes entre cómo funcionan las cosas en el CLR y cómo funcionan realmente en un servidor de base de datos. La firma del método Max() se ha definido de esta manera porque se espera que el tipo de resultado sea exactamente el mismo que el tipo de entrada en el CLR. Pero en un servidor de base de datos el resultado puede ser nulo. Por esa razón, debe enviar la entrada (aunque dependiendo de cómo escriba su consulta, podría ser suficiente para emitir la salida) a un Nullable de T.

Aquí hay una solución que parece un poco más simple que la que tiene arriba:

var version = ctx.Entries 
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId) 
    .Max(e =>(int?)e.Version); 

Espero que esto ayude.

+0

Hola Divega, mira mi respuesta, ¿ves algún problema con este enfoque? – bleevo

+0

bleevo, me temo que a menos que cambie su método para tomar argumentos IQueryable y Expression >, el método pasará implícitamente a la evaluación del lado del cliente con LINQ to Objects. Personalmente, creo que solo lanzando a T? es mas simple – divega

+0

¡Bienvenido a SO, Diego! ¡Que bueno verte aquí! –

2

No pude aceptar un no por respuesta :) He probado a continuación y funciona, aún no he comprobado el SQL generado, así que tenga cuidado, lo actualizaré una vez que haya probado más.

var test = ctx.Entries 
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId) 
    .MaxOrDefault(x => x.Version); 

public static TResult? MaxOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) 
    where TResult : struct 
{ 
    return source 
     .Select(selector) 
     .Cast<TResult?>() 
     .Max(); 
} 
+1

Esto no es una consulta L2E. Es L2O. Está bien, pero no es lo que preguntaste. :) –

+0

¡Sí, vuelve a la pizarra correcta! – bleevo

13

Pruebe esto para crear un valor predeterminado para su máximo.

int version = ctx.Entries 
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId) 
    .Max(e =>(int?)e.Version) ?? 0; 
+4

La respuesta aceptada devuelve un int nullable. Su respuesta es una forma concisa de devolver una int no anulable, que fue más útil para mí. – kevinpo

0

Prueba esto:

IEnumerable<AlertsResultset> alerts = null; 

    alerts = (from POA in SDSEntities.Context.SDS_PRODUCT_ORDER_ALERT 
       join A in SDSEntities.Context.SDS_ALERT on POA.ALERT_ID equals A.ALERT_ID 
       orderby POA.DATE_ADDED descending 
       select new AlertsResultset 
       { 
        ID = POA.PRODUCT_ORDER_ALERT_ID == null ? 0:POA.PRODUCT_ORDER_ALERT_ID , 
        ITEM_ID = POA.ORDER_ID.HasValue ? POA.ORDER_ID.Value : POA.PRODUCT_ID.Value, 
        Date = POA.DATE_ADDED.Value, 
        orderType = SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().ORDER_TYPE, 
        TransactionNumber = POA.PRODUCT_ID.HasValue ? (SDSEntities.Context.SDS_PRODUCT.Where(p => p.PRODUCT_ID == POA.PRODUCT_ID.Value).FirstOrDefault().TRANSACTION_NUMBER) : (SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().TRANSACTION_NUMBER), 
        Publisher = POA.PRODUCT_ID.HasValue ? 
        (
        SDSEntities.Context.SDS_PRODUCT.Where(p => p.PRODUCT_ID == POA.PRODUCT_ID.Value).FirstOrDefault().PRODUCT_TYPE_NUMBER == "ISSUE" ? (from prod in SDSEntities.Context.SDS_PRODUCT 
                                         join ji in SDSEntities.Context.SDS_JOURNAL_ISSUE on prod.PRODUCT_ID equals ji.PRODUCT_ID 
                                         join j in SDSEntities.Context.SDS_JOURNAL on ji.JOURNAL_ID equals j.JOURNAL_ID 
                                         where prod.PRODUCT_ID == POA.PRODUCT_ID 
                                         select new { j.PUBLISHER_NAME }).FirstOrDefault().PUBLISHER_NAME : (from prod in SDSEntities.Context.SDS_PRODUCT 
                                                          join bi in SDSEntities.Context.SDS_BOOK_INSTANCE on prod.PRODUCT_ID equals bi.PRODUCT_ID 
                                                          join b in SDSEntities.Context.SDS_BOOK on bi.BOOK_ID equals b.BOOK_ID 
                                                          where prod.PRODUCT_ID == POA.PRODUCT_ID 
                                                          select new { b.PUBLISHER_NAME }).FirstOrDefault().PUBLISHER_NAME 
       ) 
        : (SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().PUBLISHER_NAME), 
        Alert = A.ALERT_NAME, 
        AlertType = A.ALERT_TYPE, 
        IsFlagged = POA.IS_FLAGGED.Value, 
        Status = POA.ALERT_STATUS 
       }); 
4

Se puede escribir un método de extensión simple como esto, devuelve el valor por defecto de tipo T, si no existen registros y luego se aplica Max para eso o la consulta si registros existen

public static T MaxOrEmpty<T>(this IQueryable<T> query) 
{ 
    return query.DefaultIfEmpty().Max(); 
} 

y se podía utilizar de esta manera

maxId = context.Competition.Select(x=>x.CompetitionId).MaxOrEmpty(); 
+0

Lo bueno de esta solución es que puede especificar un valor predeterminado (DefaultIfEmpty (value)). Por lo tanto, no tiene que devolver un tipo que admite nulos. – robert

0

¿qué pasa con

query.Max<TSource,TResult?>(selector) 

me refiero a establecer explícitamente el tipo de resultado ser anulable

Cuestiones relacionadas