使用集合EntityFramework连接表

时间:2014-09-26 22:03:31

标签: c# entity-framework

可能是重复,但我找不到正确的方法来正确遵循。

通常我想从Employee表中检索与List相关的所有数据。类型MyEmployee包含EntitySourceID,我用它来映射EmployeeID。所以,我想检索所有与EmployeeID匹配的员工ID与List集合中的EntitySourceID。

MyEmployee类型如下:

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

我的查询如下:

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();
    }

你可以在查询中看到,当我创建新的Person对象时,我使用了List myEmployees集合中的pList.PersonID来填充Person的。

所以,我的问题是如何高效地从Employee表中检索与List集合匹配的数据,并使用pList.PersonID(来自集合)来创建返回结果?

我使用EF 6,数据库第一种方法。

另外,我没有提到。此查询产生以下异常: 无法创建“MyEmployee”类型的常量值。在此上下文中仅支持原始类型或枚举类型。

2 个答案:

答案 0 :(得分:15)

IQueryable vs IEnumerable

解决一些更深层次问题的良好开端是花一些时间发现

之间的差异

也许也介于

之间

因为虽然他们有相似的形式,但他们的目的和行为不同。

现在回到你的问题

首先,让我们举几个例子:

  • 让我们调用RAM托管的MyEmployee个实例 THE LIST
  • 集合
  • 让我们调用数据库表(最有可能是&#34;员工&#34;) THE TABLE

遗憾的是,在撰写问题时,您没有指定一些非常重要的细节。 这导致我提出4个不同的答案。 答案将根据以下两个问题的真值进行分类:

  • THE LIST 巨大吗?
  • THE TABLE 巨大?

我们有4个非常不同的案例:

  1. 不,不是
  2. 不,是
  3. 是,否
  4. 是,是
  5. 你现在可以想象第四个可能是最丑陋的。

    当列表不大时

    在案例1和案例2中,您可以从不同的角度思考您的问题:

    假设您需要根据 Precisely 1 参数(即ID)从数据库中提取 ONE (或零)记录。你应该进行加入吗?

    答案是:绝对。 看看这段代码:

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

    如果我想获取与 Precisely 2 参数相关的记录,该怎么办? 我可以用类似的方式实现这一点:

    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
    

    我故意以愚蠢的方式写出算法的最后润色(if部分)以避免额外的混淆。

    对于最后的接触,这将是一种更人性化的方法:

    ...
    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
    

    不要担心ToDictionary扩展方法。 它有 NOTHING 与EntityFramework有关(点击它查找)。

    现在..回到我们的故事:如果你想带来与15个ID相关的记录怎么办? 停止。这是怎么回事?我是否要求您为每个可能的ID数量硬编码不同的查询?

    当然不是。 只要id的数量相对较小&#34; (意味着你被某人允许,或者你自己用这个请求量级轰炸数据库)你可以很好地使用&#34;列IN列表中的参数&#34; SQL构造。

    如何指导LINQ to SQL或EF转换为&#34; x IN y&#34;操作而不是&#34; x = y&#34;操作,在SQL端?

    使用相应类型的基本数组和Contains方法。 换句话说,获得负载:

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

    但你需要一个&#34; ID列表&#34;不是MyEmployee实例的列表&#34;。 你可以很容易地把它拉下来:

    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
    

    请注意对集合的查询显示为IEnumerable<T>个实例,而不是IQueryable<T>实例,与EF或LINQ to SQL或任何其他数据库或外部数据无关服务。

    如果表格不是很大

    然后,您可以避免将EF实际用于复杂查询,仅将其用于&#34; Full table fetch&#34;,将结果临时存储在.NET进程中,并使用常规LINQ,无论您喜欢什么。

    这个故事的关键是从一开始就获取整个表格。 在你的问题中你写道:

    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
    

    只需使用以下内容进行补充:

    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 ...
    

    包装

    你可以:

    • 指示数据库完成工作,但在这种情况下,您无法在流程中发送花哨的.NET实例
    • 自己,在楼上,在.NET中完成工作

    一方或另一方(数据库或.NET进程)需要拥有所有卡(需要克隆另一方)才能执行 JOIN

    所以这只是一场妥协游戏。

    关于剩余案例

    如果 THE TABLE THE LIST 都很大,那么你就是**** d。 不 - 我只是在开玩笑。

    没有人听说过有人要求其他人在他们无法完成的时候做奇迹。

    如果这个就是这种情况,那么你必须将问题简化为大量小问题。 我建议转换成 TABLE HUGE + LIST NOT SO HUGE 问题乘以N.

    那你怎么做呢?

    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);
    
    }
    

    现在你可以说这只是一种愚弄数据库的方式,相信它几乎没有什么工作要做,而实际上我们仍然需要大量的工作和可能让其他并发客户的生活变得艰难。

    嗯 - 是的,但至少你可以减速。 您可以在各个部分之间Thread.Sleep ...您可以使用iterators(查找它们)并且实际上不会使RAM充满需要很长时间才能处理的记录,而是“#34;流式传输& #34;

    您可以更好地控制情况。

    祝你好运!

答案 1 :(得分:0)

我使用以下方法:

  • 从myEmployees列表中提取ID;
  • 检索该ID的数据并放入var query;
  • 加入mayEmployees并查询结果。

如以下示例所示:

   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();