¿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ó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
};
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)
};
from x in gj.DefaultIfEmpty()
se convierte from 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 la Join
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 una SelectMany
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 el resultSelector
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);
}
}
}
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)
};
Price = ugp.Price
puede fallar si Price
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 para Price
.
Si prefiere la notación de llamada al método, puede forzar una combinación a la izquierda usando SelectMany
combinado con DefaultIfEmpty
. 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 tipo int
). 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]
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
};
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
});
.DefaultIfEmpty()
nada: solo afecta lo que sucede cuando db.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 la Member
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.