简体   繁体   English

使用集合EntityFramework连接表

[英]Join table with collection EntityFramework

Maybe is it duplicate but I can't find proper way to do following correctly. 也许它是重复但我找不到正确的方法正确遵循。

Generally I want to retrieve all data from Employee table which are related with List. 通常我想从Employee表中检索与List相关的所有数据。 Type MyEmployee contains EntitySourceID which I use to map with EmployeeID. 类型MyEmployee包含EntitySourceID,我用它来映射EmployeeID。 So, I want to retrieve all Employees which have match EmployeeID with EntitySourceID in List collection. 所以,我想检索所有与List集合中的EntitySourceID匹配EmployeeID的Employees。

Type MyEmployee looks like: 类型MyEmployee看起来像:

public class MyEmployee 
    {
        public long PersonID { get; set; }
        public string ConnectionString { get; set; }
        public long EntitySourceID { get; set; }
        public int EntitySourceTypeID { get; set; }
    }

My query looks like: 我的查询如下:

internal IEnumerable<Person> GetPersons(List<MyEmployee> myEmployees)
    {
            return (from p in _context.Employee
                join pList in myEmployees on p.EmployeeID equals pList.EntitySourceID
                select new Person 
                {
                    PersonID = pList.PersonID,
                    FirstName = p.FirstName,
                    LastName = p.LastName,
                    Name = p.Name,
                    Suffix = p.Suffix,
                    Title = p.Title
                }).ToList();
    }

You can see in query, when I create new Person object I used pList.PersonID from List myEmployees collection to fill Person's. 您可以在查询中看到,当我创建新的Person对象时,我使用List myEmployees集合中的pList.PersonID来填充Person。

So, my question is how I can efficient retrieve data from Employee table which have match with List collection, and also use pList.PersonID (from collection) to create return result? 所以,我的问题是我如何高效地从Employee表中检索与List集合匹配的数据,还使用pList.PersonID(来自集合)来创建返回结果?

I use EF 6, database first approach. 我使用EF 6,数据库第一种方法。

Also, I didn't mention. 另外,我没有提到。 This query produce following exception: Unable to create a constant value of type 'MyEmployee'. 此查询产生以下异常:无法创建“MyEmployee”类型的常量值。 Only primitive types or enumeration types are supported in this context. 在此上下文中仅支持基元类型或枚举类型。

IQueryable vs IEnumerable IQueryable vs IEnumerable

A good start for solving some of your deeper questions would be spending some time discovering the differences between 解决一些更深层次问题的良好开端是花一些时间来发现它们之间的差异

and maybe also between 也许还介于两者之间

because while they have similar forms they differ in purpose and behavior. 因为虽然他们有相似的形式,但他们的目的和行为不同。

Now back to your question 现在回到你的问题

For starters let's name a few things: 首先让我们举几个例子:

  • Let's call the RAM hosted collection of MyEmployee instances THE LIST 让我们调用RAM托管的MyEmployee实例集合列表
  • Let's call the database table (most probably entitled "Employee(s)") THE TABLE 让我们调用数据库表(最有可能称为“Employee(s)”)

Sadly you didn't specify a few very important details while writing your question. 遗憾的是,在撰写问题时,您没有指出一些非常重要的细节。 That leads me to proposing 4 different answers. 这导致我提出4个不同的答案。 The answers will be categorized based on the truth values of the following 2 questions: 答案将根据以下两个问题的真值进行分类:

  • Is THE LIST huge? 列表是巨大的吗?
  • Is THE TABLE huge? 桌子很大吗?

We have 4 very different cases: 我们有4个非常不同的案例:

  1. No, No 不,不
  2. No, Yes 不,是的
  3. Yes, No 是的,没有
  4. Yes, Yes 是的是的

You can imagine by now that the fourth is maybe the ugliest. 你可以想象,现在第四个可能是最丑陋的。

When THE LIST is not huge 当列表不是很大

In cases 1 and 2 you could think about your problem from a different perspective: 在案例1和案例2中,您可以从不同的角度思考您的问题:

Say you need to fetch ONE (or zero) record(s) from the database based on Precisely 1 parameter which is an ID. 假设您需要根据作为ID的Precisely 1参数从数据库中获取一个 (或零)记录。 Should you be performing a JOIN ? 你应该加入吗?

The answer is: absolutely NOT . 答案是:绝对不是 Take a look at this code: 看看这段代码:

var query = from employee in _context.Employee
            where employee.EmployeeId == 23
            select employee;
var found = query.FirstOrDefault();

What if I wanted to fetch the records associated with Precisely 2 parameters? 如果我想获取与Precisely 2参数相关的记录怎么办? I could achieve that in a similar manner: 我可以用类似的方式实现这一点:

var query = from employee in _context.Employee
            where employee.EmployeeId == 23 || employee.EmployeeId == 24
            select employee;
var results = query.ToArray();

if (results.Length == 0)
   // didn't find anyone of the presumably existing records

else if (results.Length == 1) {
   if (results[0].EmployeeId == 23)
      // then we found the 23
   else
      // the other one

} else if (results.Length == 2)
   // found both, look inside to see which is which

I have intentionally written the finishing touches of the algorithms (the if part) in a silly manner in order to avoid extra confusion. 我故意以愚蠢的方式写出算法的最后润色( if部分)以避免额外的混淆。

This would be a more humane approach for the finishing touches: 对于最后的润色,这将是一种更人性化的方法:

...
var results = ... got them (see above)

var map = results.ToDictionary(keySelector: x => x.EmployeeId);
var count = map.Count; // this gives you the number of results, same as results.Length
var have23 = map.ContainsKey(23); // this tells you whether you managed to fetch a certain id
var record23 = map[23]; // this actually gives you the record
foreach (var key in map.Keys) { .. } // will iterate over the fetched ids
foreach (var record in map.Values) { .. } // will iterate over the fetched values

Worry not about the ToDictionary extension method. 不要担心ToDictionary扩展方法。 It has NOTHING to do with EntityFramework (look it up by clicking on it). 无关的EntityFramework(通过点击它看它)。

Now.. back to our story: What if you wanted to bring the records associated with 15 ids? 现在..回到我们的故事:如果你想带来与15个ID相关的记录怎么办? Stop. 停止。 Where is this going? 这是怎么回事? Am I asking you to hardcode a different query for each possible number of ids? 我是否要求您为每个可能的ID数量硬编码不同的查询?

Of course not. 当然不是。 As long as the number of ids is "relatively small" (meaning you are allowed by someone, or by yourself to bombard the database with that request magnitude) you could very well use an "column IN list of params" SQL construct. 只要id的数量“相对较小”(意味着你被某人允许,或者你自己用该请求量级轰炸数据库),你就可以很好地使用“列IN列表的params”SQL构造。

How can you instruct LINQ to SQL, or EF to translate into an "x IN y" operation instead of an "x = y" operation, on the SQL side? 如何在SQL端指示LINQ to SQL或EF转换为“x IN”操作而不是“x = y”操作?

By using a primitive array of the respective type and the Contains method. 通过使用相应类型的原始数组和Contains方法。 In other words, get a load of: 换句话说,得到一个负载:

var query = from employee in _context.Employee
            where listOfIds.Contains( employee.EmployeeId )
            select employee;
var results = query.ToArray();

But you need a "list of Ids" not a "list of MyEmployee instances". 但是你需要一个“ID列表”而不是“MyEmployee实例列表”。 You could pull that off very easily like so: 你可以很容易地把它拉下来:

List<MyEmployee> originalList = new List<MyEmployee>();
// ... say you populate this somehow, or you've received it from elsewhere

int[] listOfIds = (from employee in originalList
                   select employee.EntityId).ToArray();

// .. and then carry on with the EF query

Please note that queries on collections manifest as IEnumerable<T> instances, not as IQueryable<T> instances and have nothing to do with EF or LINQ to SQL or anyother DB or external data service. 请注意 ,对集合的查询显示为IEnumerable<T>实例,而不是IQueryable<T>实例,与EF或LINQ to SQL或任何其他DB或外部数据服务无关。

IF THE TABLE IS NOT HUGE 如果桌子不是巨大的

Then you could refrain from actually using EF with complex queries, use it just for a "Full table fetch", temporarily store the results in your .NET process and use regular LINQ however you like. 然后你可以避免在复杂查询中实际使用EF,只是将它用于“全表提取”,暂时将结果存储在.NET进程中,然后使用常规LINQ。

The key to this story is fetching the entire table from the beginning. 这个故事的关键是从一开始就获取整个表格。 In your question you wrote: 在你的问题中你写道:

return (from p in _context.Employee
            join pList in myEmployees on p.EmployeeID equals pList.EntitySourceID
            select new Person 
            {
                PersonID = pList.PersonID,
                FirstName = p.FirstName
                ... etc

Simply augment that with: 只需增加:

var entityList = _context.Employee.ToArray();

return (from p in entityList  // PLEASE NOTE THIS CHANGE ALSO
        join pList in myEmployees on p.EmployeeID equals pList.EntitySourceID
        select ...

TO WRAP IT UP 把它包起来

You can either: 你可以:

  • instruct the database to do the work but in which case you can't send it fancy .NET instances in the process 指示数据库执行工作,但在这种情况下,您无法在此过程中发送花哨的.NET实例
  • do the work yourself, upstairs, in .NET 在.NET上自己做楼上的工作

Either one side or the other (the database or the .NET process) needs to have all the cards (needs to have a clone of the other side) in order be able to perform the JOIN . 一方或另一方(数据库或.NET进程)需要拥有所有卡(需要克隆另一方)才能执行JOIN

So it's just a game of compromise. 所以这只是一场妥协游戏。

HOW ABOUT THE REMAINING CASE 如何处理剩余案例

If both THE TABLE and THE LIST are huge, then you're s****d. 如果THE TABLETHE LIST都是巨大的,那么你就是**** d。 No- I'm just kidding. 不 - 我只是在开玩笑。

Nobody heard of someone asking someone else to do wonders when they can't actually be done. 没有人听说过有人要求别人在他们无法完成的时候创造奇迹。

If this is the case, then you have to simplify the problem into a large number of smaller problems. 如果是这样的话,那么你必须使问题简化成大量的小问题。 I would suggest transforming into a TABLE HUGE + LIST NOT SO HUGE problem multiplied by N. 我建议转换成一个表巨大的 + 列表而不是那么大的问题乘以N.

So how do you go about that? 那么你怎么做呢?

List<MyEmployee> original = ...
// you take your list
// and you split it in sections of .. say 50 (which in my book is not huge for a database
// although be careful - the pressure on the database will be almost that of 50 selects running in parallel for each select)

// how do you split it?
// you could try this

public static IEnumerable<List<MyEmployee>> Split(List<MyEmployee> source, int sectionLength) {
    List<MyEmployee> buffer = new List<MyEmployee>();
    foreach (var employee in source) {
        buffer.Add(employee);
        if (buffer.Count == sectionLength) {
            yield return buffer.ToList(); // MAKE SURE YOU .ToList() the buffer in order to clone it
            buffer.Clear(); // or otherwise all resulting sections will actually point to the same instance which gets cleared and refilled over and over again
        }             
    }
    if (buffer.Count > 0)   // and if you have a remainder you need that too
       yield return buffer; // except for the last time when you don't really need to clone it
}

List<List<MyEmployee>> sections = Split(original, 50).ToList();

// and now you can use the sections
// as if you're in CASE 2 (the list is not huge but the table is)
// inside a foreach loop

List<Person> results = new List<Person>(); // prepare to accumulate results

foreach (var section in sections) {

    int[] ids = (from x in section select x.EntityID).ToArray();

    var query = from employee in _context.Employee
                where ids.Contains(employee.EmployeeId) 
                ... etc;

    var currentBatch = query.ToArray();

    results.AddRange(currentBatch);

}

Now you could say that this is simply a way of fooling the database into believing it has little work to do when in fact we're still flooding it with a lot of work and maybe making life hard for the other concurrent clients. 现在你可以说这只是一种愚弄数据库的方式,相信它几乎没有什么工作要做,实际上我们仍然充斥着大量的工作, 也许让其他并发客户的生活变得艰难。

Well- yeah, but at least you could throttle down. 嗯,是的,但至少你可以节制下来。 You could Thread.Sleep between sections... You could use iterators (look them up) and actually not flood the RAM with records that will take a long time to process anyway but rather "stream things". 您可以在各部分之间使用Thread.Sleep ...您可以使用iterators (查找它们)并且实际上不会使RAM记录需要很长时间才能处理,而是“流式传输”。

You have more control over the situation. 你可以更好地控制局面。

Good luck! 祝好运!

I use following approach: 我使用以下方法:

  • Extract IDs from myEmployees list; 从myEmployees列表中提取ID;
  • Retrieve data for that IDs and put in var query; 检索该ID的数据并放入var查询;
  • Join mayEmployees and query result. 加入mayEmployees和查询结果。

Like in following example: 如下例所示:

   long[] myEmployeesIDs  = myEmployees.Select(p => p.EntitySourceID).ToArray();

       var query = (from e in _context.Employees
                 where myEmployeesIDs.Contains(e.EmployeID)
                 select new Person
                 {
                     PersonID = e.EmployeeID 
                 }).ToList();

       return  (from m in myEmployees
                join q in query on m.EntitySourceID equals q.PersonID
                select new Person
                {
                    PersonID  = i.PersonID,
                    ...
                }).ToList();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM