¿Cómo cambio esta consulta para que devuelva todos los u.usergroups?
from u in usergroups
from p in u.UsergroupPrices
select new UsergroupPricesList
{
UsergroupID = u.UsergroupID,
UsergroupName = u.UsergroupName,
Price = p.Price
};
c#
linq
entity-framework
linq-to-entities
left-join
Lasse Edsvik
fuente
fuente
Respuestas:
adaptado de MSDN, cómo unirse a la izquierda usando EF 4
var query = from u in usergroups join p in UsergroupPrices on u.UsergroupID equals p.UsergroupID into gj from x in gj.DefaultIfEmpty() select new { UsergroupID = u.UsergroupID, UsergroupName = u.UsergroupName, Price = (x == null ? String.Empty : x.Price) };
fuente
from x in gj.DefaultIfEmpty()
se conviertefrom p in gj.DefaultIfEmpty()
. docs.microsoft.com/en-us/ef/core/querying/…Puede ser un poco exagerado, pero escribí un método de extensión, por lo que puede hacer un
LeftJoin
uso de laJoin
sintaxis (al menos en la notación de llamada al método):persons.LeftJoin( phoneNumbers, person => person.Id, phoneNumber => phoneNumber.PersonId, (person, phoneNumber) => new { Person = person, PhoneNumber = phoneNumber?.Number } );
Mi código no hace más que agregar una
GroupJoin
y unaSelectMany
llamada al árbol de expresión actual. Sin embargo, parece bastante complicado porque tengo que construir las expresiones yo mismo y modificar el árbol de expresiones especificado por el usuario en elresultSelector
parámetro para mantener todo el árbol traducible por LINQ-to-Entities.public static class LeftJoinExtension { public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>( this IQueryable<TOuter> outer, IQueryable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<TOuter, TInner, TResult>> resultSelector) { MethodInfo groupJoin = typeof (Queryable).GetMethods() .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] GroupJoin[TOuter,TInner,TKey,TResult](System.Linq.IQueryable`1[TOuter], System.Collections.Generic.IEnumerable`1[TInner], System.Linq.Expressions.Expression`1[System.Func`2[TOuter,TKey]], System.Linq.Expressions.Expression`1[System.Func`2[TInner,TKey]], System.Linq.Expressions.Expression`1[System.Func`3[TOuter,System.Collections.Generic.IEnumerable`1[TInner],TResult]])") .MakeGenericMethod(typeof (TOuter), typeof (TInner), typeof (TKey), typeof (LeftJoinIntermediate<TOuter, TInner>)); MethodInfo selectMany = typeof (Queryable).GetMethods() .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] SelectMany[TSource,TCollection,TResult](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Collections.Generic.IEnumerable`1[TCollection]]], System.Linq.Expressions.Expression`1[System.Func`3[TSource,TCollection,TResult]])") .MakeGenericMethod(typeof (LeftJoinIntermediate<TOuter, TInner>), typeof (TInner), typeof (TResult)); var groupJoinResultSelector = (Expression<Func<TOuter, IEnumerable<TInner>, LeftJoinIntermediate<TOuter, TInner>>>) ((oneOuter, manyInners) => new LeftJoinIntermediate<TOuter, TInner> {OneOuter = oneOuter, ManyInners = manyInners}); MethodCallExpression exprGroupJoin = Expression.Call(groupJoin, outer.Expression, inner.Expression, outerKeySelector, innerKeySelector, groupJoinResultSelector); var selectManyCollectionSelector = (Expression<Func<LeftJoinIntermediate<TOuter, TInner>, IEnumerable<TInner>>>) (t => t.ManyInners.DefaultIfEmpty()); ParameterExpression paramUser = resultSelector.Parameters.First(); ParameterExpression paramNew = Expression.Parameter(typeof (LeftJoinIntermediate<TOuter, TInner>), "t"); MemberExpression propExpr = Expression.Property(paramNew, "OneOuter"); LambdaExpression selectManyResultSelector = Expression.Lambda(new Replacer(paramUser, propExpr).Visit(resultSelector.Body), paramNew, resultSelector.Parameters.Skip(1).First()); MethodCallExpression exprSelectMany = Expression.Call(selectMany, exprGroupJoin, selectManyCollectionSelector, selectManyResultSelector); return outer.Provider.CreateQuery<TResult>(exprSelectMany); } private class LeftJoinIntermediate<TOuter, TInner> { public TOuter OneOuter { get; set; } public IEnumerable<TInner> ManyInners { get; set; } } private class Replacer : ExpressionVisitor { private readonly ParameterExpression _oldParam; private readonly Expression _replacement; public Replacer(ParameterExpression oldParam, Expression replacement) { _oldParam = oldParam; _replacement = replacement; } public override Expression Visit(Expression exp) { if (exp == _oldParam) { return _replacement; } return base.Visit(exp); } } }
fuente
Por favor, haga su vida más fácil (no use unirse al grupo):
var query = from ug in UserGroups from ugp in UserGroupPrices.Where(x => x.UserGroupId == ug.Id).DefaultIfEmpty() select new { UserGroupID = ug.UserGroupID, UserGroupName = ug.UserGroupName, Price = ugp != null ? ugp.Price : 0 //this is to handle nulls as even when Price is non-nullable prop it may come as null from SQL (result of Left Outer Join) };
fuente
Price = ugp.Price
puede fallar siPrice
es una propiedad que no acepta valores NULL y, sin embargo, la combinación izquierda no da ningún resultado.ugp == NULL
y establecer un valor predeterminado paraPrice
.Si prefiere la notación de llamada al método, puede forzar una combinación a la izquierda usando
SelectMany
combinado conDefaultIfEmpty
. Al menos en Entity Framework 6 golpeando SQL Server. Por ejemplo:using(var ctx = new MyDatabaseContext()) { var data = ctx .MyTable1 .SelectMany(a => ctx.MyTable2 .Where(b => b.Id2 == a.Id1) .DefaultIfEmpty() .Select(b => new { a.Id1, a.Col1, Col2 = b == null ? (int?) null : b.Col2, })); }
(Tenga en cuenta que
MyTable2.Col2
es una columna de tipoint
). El SQL generado se verá así:SELECT [Extent1].[Id1] AS [Id1], [Extent1].[Col1] AS [Col1], CASE WHEN ([Extent2].[Col2] IS NULL) THEN CAST(NULL AS int) ELSE CAST( [Extent2].[Col2] AS int) END AS [Col2] FROM [dbo].[MyTable1] AS [Extent1] LEFT OUTER JOIN [dbo].[MyTable2] AS [Extent2] ON [Extent2].[Id2] = [Extent1].[Id1]
fuente
Para 2 y más combinaciones izquierdas (unión izquierda creatorUser e iniciatorUser)
IQueryable<CreateRequestModel> queryResult = from r in authContext.Requests join candidateUser in authContext.AuthUsers on r.CandidateId equals candidateUser.Id join creatorUser in authContext.AuthUsers on r.CreatorId equals creatorUser.Id into gj from x in gj.DefaultIfEmpty() join initiatorUser in authContext.AuthUsers on r.InitiatorId equals initiatorUser.Id into init from x1 in init.DefaultIfEmpty() where candidateUser.UserName.Equals(candidateUsername) select new CreateRequestModel { UserName = candidateUser.UserName, CreatorId = (x == null ? String.Empty : x.UserName), InitiatorId = (x1 == null ? String.Empty : x1.UserName), CandidateId = candidateUser.UserName };
fuente
Pude hacer esto llamando a DefaultIfEmpty () en el modelo principal. Esto me permitió unirme a la izquierda en entidades cargadas de forma diferida, me parece más legible:
var complaints = db.Complaints.DefaultIfEmpty() .Where(x => x.DateStage1Complete == null || x.DateStage2Complete == null) .OrderBy(x => x.DateEntered) .Select(x => new { ComplaintID = x.ComplaintID, CustomerName = x.Customer.Name, CustomerAddress = x.Customer.Address, MemberName = x.Member != null ? x.Member.Name: string.Empty, AllocationName = x.Allocation != null ? x.Allocation.Name: string.Empty, CategoryName = x.Category != null ? x.Category.Ssl_Name : string.Empty, Stage1Start = x.Stage1StartDate, Stage1Expiry = x.Stage1_ExpiryDate, Stage2Start = x.Stage2StartDate, Stage2Expiry = x.Stage2_ExpiryDate });
fuente
.DefaultIfEmpty()
nada: solo afecta lo que sucede cuandodb.Complains
está vacío.db.Complains.Where(...).OrderBy(...).Select(x => new { ..., MemberName = x.Member != null ? x.Member.Name : string.Empty, ... })
, sin ninguno.DefaultIfEmpty()
, ya realizaría una combinación izquierda (suponiendo que laMember
propiedad esté marcada como opcional).Si UserGroups tiene una relación de uno a muchos con la tabla UserGroupPrices, entonces en EF, una vez que la relación se define en código como:
//In UserGroups Model public List<UserGroupPrices> UserGrpPriceList {get;set;} //In UserGroupPrices model public UserGroups UserGrps {get;set;}
Puede extraer el conjunto de resultados unido a la izquierda simplemente de esta manera:
var list = db.UserGroupDbSet.ToList();
asumiendo que su DbSet para la tabla de la izquierda es UserGroupDbSet, que incluirá UserGrpPriceList, que es una lista de todos los registros asociados de la tabla de la derecha.
fuente