join-on-equals-into
句を使用せずにC#LINQでオブジェクトへの左外部結合を実行する方法where
節でそれをする方法はありますか?正しい問題:内部結合は簡単なので、このような解決策があります。
List<JoinPair> innerFinal = (from l in lefts from r in rights where l.Key == r.Key
select new JoinPair { LeftId = l.Id, RightId = r.Id})
しかし、左外部結合には解決策が必要です。私のものはこのようなものですが、それは機能していません
List< JoinPair> leftFinal = (from l in lefts from r in rights
select new JoinPair {
LeftId = l.Id,
RightId = ((l.Key==r.Key) ? r.Id : 0
})
joinPairはクラスです。
public class JoinPair { long leftId; long rightId; }
に述べたように:
var q =
from c in categories
join p in products on c.Category equals p.Category into ps
from p in ps.DefaultIfEmpty()
select new { Category = c, ProductName = p == null ? "(No products)" : p.ProductName };
データベース駆動のLINQプロバイダを使用する場合は、かなり読みやすい左外部結合を次のように書くことができます。
from maintable in Repo.T_Whatever
from xxx in Repo.T_ANY_TABLE.Where(join condition).DefaultIfEmpty()
DefaultIfEmpty()
を省略すると、内部結合があります。
受け入れられた答えを取りなさい:
from c in categories
join p in products on c equals p.Category into ps
from p in ps.DefaultIfEmpty()
この構文は非常に紛らわしいので、MULTIPLEテーブルを結合したままにしたい場合にどのように機能するかは明確ではありません。
注 from alias in Repo.whatever.Where(condition).DefaultIfEmpty()
はouter-apply/left-join-lateralと同じであることに注意する必要があります。 t行ごとの値を導入します(別名、実際の外部適用)。 Linq-2-Objectsではこれを行わないでください(Linq-to-Objectsを使用するときにDBオプティマイザがないため)。
詳細例
var query2 = (
from users in Repo.T_User
from mappings in Repo.T_User_Group
.Where(mapping => mapping.USRGRP_USR == users.USR_ID)
.DefaultIfEmpty() // <== makes join left join
from groups in Repo.T_Group
.Where(gruppe => gruppe.GRP_ID == mappings.USRGRP_GRP)
.DefaultIfEmpty() // <== makes join left join
// where users.USR_Name.Contains(keyword)
// || mappings.USRGRP_USR.Equals(666)
// || mappings.USRGRP_USR == 666
// || groups.Name.Contains(keyword)
select new
{
UserId = users.USR_ID
,UserName = users.USR_User
,UserGroupId = groups.ID
,GroupName = groups.Name
}
);
var xy = (query2).ToList();
LINQ 2 SQLと一緒に使用すると、以下の非常に読みやすいSQLクエリにうまく変換されます。
SELECT
users.USR_ID AS UserId
,users.USR_User AS UserName
,groups.ID AS UserGroupId
,groups.Name AS GroupName
FROM T_User AS users
LEFT JOIN T_User_Group AS mappings
ON mappings.USRGRP_USR = users.USR_ID
LEFT JOIN T_Group AS groups
ON groups.GRP_ID == mappings.USRGRP_GRP
編集:
より複雑な例については、「 SQL ServerクエリをLinqクエリに変換する 」も参照してください。
また、(Linq-2-SQLの代わりに)Linq-2-Objectsでそれをやっているなら、(昔のやり方でそれをやるべきです)フルスキャンを強制し、インデックス検索を利用しません。
var query2 = (
from users in Repo.T_Benutzer
join mappings in Repo.T_Benutzer_Benutzergruppen on mappings.BEBG_BE equals users.BE_ID into tmpMapp
join groups in Repo.T_Benutzergruppen on groups.ID equals mappings.BEBG_BG into tmpGroups
from mappings in tmpMapp.DefaultIfEmpty()
from groups in tmpGroups.DefaultIfEmpty()
select new
{
UserId = users.BE_ID
,UserName = users.BE_User
,UserGroupId = mappings.BEBG_BG
,GroupName = groups.Name
}
);
ラムダ式を使う
db.Categories
.GroupJoin(
db.Products,
Category => Category.CategoryId,
Product => Product.CategoryId,
(x, y) => new { Category = x, Products = y })
.SelectMany(
xy => xy.Products.DefaultIfEmpty(),
(x, y) => new { Category = x.Category, Product = y })
.Select(s => new
{
CategoryName = s.Category.Name,
ProductName = s.Product.Name
})
この example を見てください。このクエリは機能するはずです。
var leftFinal = from left in lefts
join right in rights on left equals right.Left into leftRights
from leftRight in leftRights.DefaultIfEmpty()
select new { LeftId = left.Id, RightId = left.Key==leftRight.Key ? leftRight.Id : 0 };
今拡張メソッドとして:
public static class LinqExt
{
public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(this IEnumerable<TLeft> left, IEnumerable<TRight> right, Func<TLeft, TKey> leftKey, Func<TRight, TKey> rightKey,
Func<TLeft, TRight, TResult> result)
{
return left.GroupJoin(right, leftKey, rightKey, (l, r) => new { l, r })
.SelectMany(
o => o.r.DefaultIfEmpty(),
(l, r) => new { lft= l.l, rght = r })
.Select(o => result.Invoke(o.lft, o.rght));
}
}
通常joinを使うのと同じように使います。
var contents = list.LeftOuterJoin(list2,
l => l.country,
r => r.name,
(l, r) => new { count = l.Count(), l.country, l.reason, r.people })
これが時間を節約することを願っています。
拡張メソッドによる左外部結合の実装は次のようになります。
public static IEnumerable<Result> LeftJoin<TOuter, TInner, TKey, Result>(
this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
, Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
{
if (outer == null)
throw new ArgumentException("outer");
if (inner == null)
throw new ArgumentException("inner");
if (outerKeySelector == null)
throw new ArgumentException("outerKeySelector");
if (innerKeySelector == null)
throw new ArgumentException("innerKeySelector");
if (resultSelector == null)
throw new ArgumentException("resultSelector");
return LeftJoinImpl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer ?? EqualityComparer<TKey>.Default);
}
static IEnumerable<Result> LeftJoinImpl<TOuter, TInner, TKey, Result>(
IEnumerable<TOuter> outer, IEnumerable<TInner> inner
, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
, Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
{
var innerLookup = inner.ToLookup(innerKeySelector, comparer);
foreach (var outerElment in outer)
{
var outerKey = outerKeySelector(outerElment);
var innerElements = innerLookup[outerKey];
if (innerElements.Any())
foreach (var innerElement in innerElements)
yield return resultSelector(outerElment, innerElement);
else
yield return resultSelector(outerElment, default(TInner));
}
}
結果セレクタは、null要素を処理する必要があります。 Fx.
static void Main(string[] args)
{
var inner = new[] { Tuple.Create(1, "1"), Tuple.Create(2, "2"), Tuple.Create(3, "3") };
var outer = new[] { Tuple.Create(1, "11"), Tuple.Create(2, "22") };
var res = outer.LeftJoin(inner, item => item.Item1, item => item.Item1, (it1, it2) =>
new { Key = it1.Item1, V1 = it1.Item2, V2 = it2 != null ? it2.Item2 : default(string) });
foreach (var item in res)
Console.WriteLine(string.Format("{0}, {1}, {2}", item.Key, item.V1, item.V2));
}
この例を見てください
class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public static void LeftOuterJoinExample()
{
Person magnus = new Person {ID = 1, FirstName = "Magnus", LastName = "Hedlund"};
Person terry = new Person {ID = 2, FirstName = "Terry", LastName = "Adams"};
Person charlotte = new Person {ID = 3, FirstName = "Charlotte", LastName = "Weiss"};
Person arlene = new Person {ID = 4, FirstName = "Arlene", LastName = "Huff"};
Pet barley = new Pet {Name = "Barley", Owner = terry};
Pet boots = new Pet {Name = "Boots", Owner = terry};
Pet whiskers = new Pet {Name = "Whiskers", Owner = charlotte};
Pet bluemoon = new Pet {Name = "Blue Moon", Owner = terry};
Pet daisy = new Pet {Name = "Daisy", Owner = magnus};
// Create two lists.
List<Person> people = new List<Person> {magnus, terry, charlotte, arlene};
List<Pet> pets = new List<Pet> {barley, boots, whiskers, bluemoon, daisy};
var query = from person in people
where person.ID == 4
join pet in pets on person equals pet.Owner into personpets
from petOrNull in personpets.DefaultIfEmpty()
select new { Person=person, Pet = petOrNull};
foreach (var v in query )
{
Console.WriteLine("{0,-15}{1}", v.Person.FirstName + ":", (v.Pet == null ? "Does not Exist" : v.Pet.Name));
}
}
// This code produces the following output:
//
// Magnus: Daisy
// Terry: Barley
// Terry: Boots
// Terry: Blue Moon
// Charlotte: Whiskers
// Arlene:
その要素がinclude elements from the left
であってもhas no matches in the right
ができるようになりました。私たちの場合はArlene
を検索しました。
これが参照です
これは一般的な形式です(他の回答で既に提供されているように)
var c =
from a in alpha
join b in beta on b.field1 equals a.field1 into b_temp
from b_value in b_temp.DefaultIfEmpty()
select new { Alpha = a, Beta = b_value };
しかし、これが実際に何を意味するのかを明確にすることを私は望んでいるという説明がここにあります!
join b in beta on b.field1 equals a.field1 into b_temp
基本的には、右側のエントリ( 'b'内のエントリ)に対して実質的にnull 'rows'を含む個別の結果セットb_tempを作成します。
そして次の行:
from b_value in b_temp.DefaultIfEmpty()
..その結果セットを繰り返し、右側の 'row'にデフォルトのnull値を設定し、右側のrow joinの結果を 'b_value'の値(つまり右側の値)に設定します。一致するレコードがある場合は、一方で、一致するレコードがない場合は「null」です。
今度は、右側が別のLINQクエリの結果である場合、それは匿名型で構成され、それは 'something'または 'null'のどちらかになります。それが列挙可能である場合(例えば、List - MyObjectBが2つのフィールドを持つクラスである場合)、どのデフォルトの 'null'値がそのプロパティに使用されるかについて特定することは可能です:
var c =
from a in alpha
join b in beta on b.field1 equals a.field1 into b_temp
from b_value in b_temp.DefaultIfEmpty( new MyObjectB { Field1 = String.Empty, Field2 = (DateTime?) null })
select new { Alpha = a, Beta_field1 = b_value.Field1, Beta_field2 = b_value.Field2 };
これは 'b'自体がnullではないことを保証します(しかしそのプロパティはあなたが指定したデフォルトのnull値を使ってnullになることができます)そしてこれはb_valueのnull参照例外を得ることなくb_valueのプロパティをチェックすることを可能にします。 NULL可能なDateTimeの場合は、(DefaultTime?)の型(null許容のDateTime)を 'DefaultIfEmpty'の指定でnullの 'Type'として指定する必要があります(これは 'ネイティブではない型にも適用されます)。 'null、例えばdouble、float).
上記の構文を単純に連鎖させることで、複数の左外部結合を実行できます。
これは、2つ以上のテーブルを結合する必要がある場合の例です。
from d in context.dc_tpatient_bookingd
join bookingm in context.dc_tpatient_bookingm
on d.bookingid equals bookingm.bookingid into bookingmGroup
from m in bookingmGroup.DefaultIfEmpty()
join patient in dc_tpatient
on m.prid equals patient.prid into patientGroup
from p in patientGroup.DefaultIfEmpty()
3つのテーブルがあります:person、school、およびemployees_schools。個人を彼らが勉強する学校に接続します。id= 6の人物への参照は、people_schoolsテーブルにはありません。しかし、id = 6の人物は結果左結合グリッドに表示されます。
List<Person> persons = new List<Person>
{
new Person { id = 1, name = "Alex", phone = "4235234" },
new Person { id = 2, name = "Bob", phone = "0014352" },
new Person { id = 3, name = "Sam", phone = "1345" },
new Person { id = 4, name = "Den", phone = "3453452" },
new Person { id = 5, name = "Alen", phone = "0353012" },
new Person { id = 6, name = "Simon", phone = "0353012" }
};
List<School> schools = new List<School>
{
new School { id = 1, name = "Saint. John's school"},
new School { id = 2, name = "Public School 200"},
new School { id = 3, name = "Public School 203"}
};
List<PersonSchool> persons_schools = new List<PersonSchool>
{
new PersonSchool{id_person = 1, id_school = 1},
new PersonSchool{id_person = 2, id_school = 2},
new PersonSchool{id_person = 3, id_school = 3},
new PersonSchool{id_person = 4, id_school = 1},
new PersonSchool{id_person = 5, id_school = 2}
//a relation to the person with id=6 is absent
};
var query = from person in persons
join person_school in persons_schools on person.id equals person_school.id_person
into persons_schools_joined
from person_school_joined in persons_schools_joined.DefaultIfEmpty()
from school in schools.Where(var_school => person_school_joined == null ? false : var_school.id == person_school_joined.id_school).DefaultIfEmpty()
select new { Person = person.name, School = school == null ? String.Empty : school.name };
foreach (var elem in query)
{
System.Console.WriteLine("{0},{1}", elem.Person, elem.School);
}
Join構文による左結合のように機能する拡張メソッド
public static class LinQExtensions
{
public static IEnumerable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer, IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector)
{
return outer.GroupJoin(
inner,
outerKeySelector,
innerKeySelector,
(outerElement, innerElements) => resultSelector(outerElement, innerElements.FirstOrDefault()));
}
}
.NETコアで書いたところで、期待通りに動いているようです。
小テスト:
var Ids = new List<int> { 1, 2, 3, 4};
var items = new List<Tuple<int, string>>
{
new Tuple<int, string>(1,"a"),
new Tuple<int, string>(2,"b"),
new Tuple<int, string>(4,"d"),
new Tuple<int, string>(5,"e"),
};
var result = Ids.LeftJoin(
items,
id => id,
item => item.Item1,
(id, item) => item ?? new Tuple<int, string>(id, "not found"));
result.ToList()
Count = 4
[0]: {(1, a)}
[1]: {(2, b)}
[2]: {(3, not found)}
[3]: {(4, d)}
Linq C#で左外部結合を実行する
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Child
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public class JoinTest
{
public static void LeftOuterJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
Child barley = new Child { Name = "Barley", Owner = terry };
Child boots = new Child { Name = "Boots", Owner = terry };
Child whiskers = new Child { Name = "Whiskers", Owner = charlotte };
Child bluemoon = new Child { Name = "Blue Moon", Owner = terry };
Child daisy = new Child { Name = "Daisy", Owner = magnus };
// Create two lists.
List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
List<Child> childs = new List<Child> { barley, boots, whiskers, bluemoon, daisy };
var query = from person in people
join child in childs
on person equals child.Owner into gj
from subpet in gj.DefaultIfEmpty()
select new
{
person.FirstName,
ChildName = subpet!=null? subpet.Name:"No Child"
};
// PetName = subpet?.Name ?? String.Empty };
foreach (var v in query)
{
Console.WriteLine($"{v.FirstName + ":",-25}{v.ChildName}");
}
}
// This code produces the following output:
//
// Magnus: Daisy
// Terry: Barley
// Terry: Boots
// Terry: Blue Moon
// Charlotte: Whiskers
// Arlene: No Child
これは、内部結合と左外部結合のLINQ構文と比較したSQL構文です。左外部結合:
http://www.ozkary.com/2011/07/linq-to-entity-inner-and-left-joins.html
「次の例では、productとcategoryの間のグループ結合を行います。これは基本的には左結合です。categoryテーブルが空の場合でもinto式はデータを返します。categoryテーブルのプロパティにアクセスするには、列挙型結果から選択する必要があります。 catList.DefaultIfEmpty()ステートメントにfrom clを追加することによって。
あなたがMoreLinqエクステンションを手に入れたなら、私は今、同種および異種の両方の左結合のサポートがあることを付け加えたいと思います。
http://morelinq.github.io/2.8/ref/api/html/Overload_MoreLinq_MoreEnumerable_LeftJoin.htm
例:
//Pretend a ClientCompany object and an Employee object both have a ClientCompanyID key on them
return DataContext.ClientCompany
.LeftJoin(DataContext.Employees, //Table being joined
company => company.ClientCompanyID, //First key
employee => employee.ClientCompanyID, //Second Key
company => new {company, employee = (Employee)null}, //Result selector when there isn't a match
(company, employee) => new { company, employee }); //Result selector when there is a match
編集:
振り返ってみるとこれはうまくいくかもしれませんが、morelinqはクエリをSQLに変換しないため、IQueryableをIEnumerableに変換します。
ここで説明されているように、代わりにGroupJoinを使用できます。 https://stackoverflow.com/a/24273804/4251433
これにより、後でさらに論理操作を行う必要がある場合に備えて、確実にIQueryableとして使用できます。
これは、メソッド構文を使用した、かなり理解しやすいバージョンです。
IEnumerable<JoinPair> outerLeft =
lefts.SelectMany(l =>
rights.Where(r => l.Key == r.Key)
.DefaultIfEmpty(new Item())
.Select(r => new JoinPair { LeftId = l.Id, RightId = r.Id }));
もしあなたが参加して何かをフィルタリングする必要があるなら、それは参加の外側で行うことができます。コレクションを作成した後にフィルタをかけることができます。
この場合、結合条件でこれを実行すると、返される行が少なくなります。
三項条件が使用されています(= n == null ? "__" : n.MonDayNote,)
オブジェクトがnull
の場合(つまり一致しない場合)、?
の後にあるものを返します。この場合は__
。
そうでなければ、:
、n.MonDayNote
の後にあるものを返します。
私が私自身の問題から始めた場所である他の貢献者に感謝します。
var schedLocations = (from f in db.RAMS_REVENUE_LOCATIONS
join n in db.RAMS_LOCATION_PLANNED_MANNING on f.revenueCenterID equals
n.revenueCenterID into lm
from n in lm.DefaultIfEmpty()
join r in db.RAMS_LOCATION_SCHED_NOTE on f.revenueCenterID equals r.revenueCenterID
into locnotes
from r in locnotes.DefaultIfEmpty()
where f.LocID == nLocID && f.In_Use == true && f.revenueCenterID > 1000
orderby f.Areano ascending, f.Locname ascending
select new
{
Facname = f.Locname,
f.Areano,
f.revenueCenterID,
f.Locabbrev,
// MonNote = n == null ? "__" : n.MonDayNote,
MonNote = n == null ? "__" : n.MonDayNote,
TueNote = n == null ? "__" : n.TueDayNote,
WedNote = n == null ? "__" : n.WedDayNote,
ThuNote = n == null ? "__" : n.ThuDayNote,
FriNote = n == null ? "__" : n.FriDayNote,
SatNote = n == null ? "__" : n.SatDayNote,
SunNote = n == null ? "__" : n.SunDayNote,
MonEmpNbr = n == null ? 0 : n.MonEmpNbr,
TueEmpNbr = n == null ? 0 : n.TueEmpNbr,
WedEmpNbr = n == null ? 0 : n.WedEmpNbr,
ThuEmpNbr = n == null ? 0 : n.ThuEmpNbr,
FriEmpNbr = n == null ? 0 : n.FriEmpNbr,
SatEmpNbr = n == null ? 0 : n.SatEmpNbr,
SunEmpNbr = n == null ? 0 : n.SunEmpNbr,
SchedMondayDate = n == null ? dMon : n.MondaySchedDate,
LocNotes = r == null ? "Notes: N/A" : r.LocationNote
}).ToList();
Func<int, string> LambdaManning = (x) => { return x == 0 ? "" : "Manning:" + x.ToString(); };
DataTable dt_ScheduleMaster = PsuedoSchedule.Tables["ScheduleMasterWithNotes"];
var schedLocations2 = schedLocations.Where(x => x.SchedMondayDate == dMon);
class Program
{
List<Employee> listOfEmp = new List<Employee>();
List<Department> listOfDepart = new List<Department>();
public Program()
{
listOfDepart = new List<Department>(){
new Department { Id = 1, DeptName = "DEV" },
new Department { Id = 2, DeptName = "QA" },
new Department { Id = 3, DeptName = "BUILD" },
new Department { Id = 4, DeptName = "SIT" }
};
listOfEmp = new List<Employee>(){
new Employee { Empid = 1, Name = "Manikandan",DepartmentId=1 },
new Employee { Empid = 2, Name = "Manoj" ,DepartmentId=1},
new Employee { Empid = 3, Name = "Yokesh" ,DepartmentId=0},
new Employee { Empid = 3, Name = "Purusotham",DepartmentId=0}
};
}
static void Main(string[] args)
{
Program ob = new Program();
ob.LeftJoin();
Console.ReadLine();
}
private void LeftJoin()
{
listOfEmp.GroupJoin(listOfDepart.DefaultIfEmpty(), x => x.DepartmentId, y => y.Id, (x, y) => new { EmpId = x.Empid, EmpName = x.Name, Dpt = y.FirstOrDefault() != null ? y.FirstOrDefault().DeptName : null }).ToList().ForEach
(z =>
{
Console.WriteLine("Empid:{0} EmpName:{1} Dept:{2}", z.EmpId, z.EmpName, z.Dpt);
});
}
}
class Employee
{
public int Empid { get; set; }
public string Name { get; set; }
public int DepartmentId { get; set; }
}
class Department
{
public int Id { get; set; }
public string DeptName { get; set; }
}
簡単な方法は、Letキーワードを使用することです。これは私のために動作します。
from AItem in Db.A
Let BItem = Db.B.Where(x => x.id == AItem.id ).FirstOrDefault()
Where SomeCondition
Select new YourViewModel
{
X1 = AItem.a,
X2 = AItem.b,
X3 = BItem.c
}
これは、左結合のシミュレーションです。 Bテーブルの各アイテムがAアイテムと一致しない場合、BItemはnullを返します
LEFT OUTER JOIN の簡単な解決法
var setA = context.SetA;
var setB = context.SetB.Select(st=>st.Id).Distinct().ToList();
var leftOuter = setA.Where(stA=> !setB.Contains(stA.Id));
ノート :
(from a in db.Assignments
join b in db.Deliveryboys on a.AssignTo equals b.EmployeeId
//from d in eGroup.DefaultIfEmpty()
join c in db.Deliveryboys on a.DeliverTo equals c.EmployeeId into eGroup2
from e in eGroup2.DefaultIfEmpty()
where (a.Collected == false)
select new
{
OrderId = a.OrderId,
DeliveryBoyID = a.AssignTo,
AssignedBoyName = b.Name,
Assigndate = a.Assigndate,
Collected = a.Collected,
CollectedDate = a.CollectedDate,
CollectionBagNo = a.CollectionBagNo,
DeliverTo = e == null ? "Null" : e.Name,
DeliverDate = a.DeliverDate,
DeliverBagNo = a.DeliverBagNo,
Delivered = a.Delivered
});