Как мне изменить этот запрос, чтобы он возвращал все 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
Лассе Эдсвик
источник
источник
Ответы:
адаптировано из MSDN, как покинуть соединение с помощью 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()
становитсяfrom p in gj.DefaultIfEmpty()
. docs.microsoft.com/en-us/ef/core/querying/…Это может быть немного излишним, но я написал метод расширения, поэтому вы можете
LeftJoin
использоватьJoin
синтаксис (по крайней мере, в обозначении вызова метода):persons.LeftJoin( phoneNumbers, person => person.Id, phoneNumber => phoneNumber.PersonId, (person, phoneNumber) => new { Person = person, PhoneNumber = phoneNumber?.Number } );
Мой код не делает ничего, кроме добавления
GroupJoin
иSelectMany
вызова к текущему дереву выражения. Тем не менее, это выглядит довольно сложно, потому что мне нужно самому создавать выражения и изменять дерево выражений, указанное пользователем вresultSelector
параметре, чтобы все дерево оставалось переводимым с помощью 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); } } }
источник
Пожалуйста, упростите себе жизнь (не используйте присоединение к группе):
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
может завершиться ошибкой, еслиPrice
свойство не допускает значения NULL, а левое соединение не дает никаких результатов.ugp == NULL
и установить значение по умолчанию дляPrice
.Если вы предпочитаете нотацию вызова метода, вы можете принудительно выполнить левое соединение, используя в
SelectMany
сочетании сDefaultIfEmpty
. По крайней мере, на Entity Framework 6, поражающем SQL Server. Например: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, })); }
(Обратите внимание, что
MyTable2.Col2
это столбец типаint
). Сгенерированный SQL будет выглядеть так: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]
источник
Для 2 и более левых соединений (левое присоединение creatorUser и initiatorUser)
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 };
источник
Я смог сделать это, вызвав DefaultIfEmpty () в основной модели. Это позволило мне покинуть соединение с ленивыми загруженными объектами, что мне кажется более читаемым:
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()
: это влияет только на то, что происходит, когдаdb.Complains
он пустой.db.Complains.Where(...).OrderBy(...).Select(x => new { ..., MemberName = x.Member != null ? x.Member.Name : string.Empty, ... })
без.DefaultIfEmpty()
них уже будет выполнять левое соединение (при условии, чтоMember
свойство помечено как необязательное).Если UserGroups имеют отношение «один ко многим» с таблицей UserGroupPrices, то в EF, как только отношение определено в коде, например:
//In UserGroups Model public List<UserGroupPrices> UserGrpPriceList {get;set;} //In UserGroupPrices model public UserGroups UserGrps {get;set;}
Вы можете вытащить левый объединенный набор результатов просто так:
var list = db.UserGroupDbSet.ToList();
предполагая, что ваш DbSet для левой таблицы - это UserGroupDbSet, который будет включать UserGrpPriceList, который представляет собой список всех связанных записей из правой таблицы.
источник