Linq和大型数据集的内存/效率

时间:2014-12-04 15:43:42

标签: performance linq memory

所以你知道我来自的背景,我已经是一名专业程序员超过12年了。到目前为止,我最好的语言是C#,但我已经完成了C,C ++和最近的ObjectiveC。我在数据库中访问数据做了很多工作,但我没有像大多数人那样完成UI工作(除了IOS)。

最近我开始在C#中使用实体框架来完成工作,我必须说我希望我能早点发现它。我不会说它是自切片面包以来最好的东西,但它非常接近。使用它一段时间之后,它让我思考最佳实践和用法,而不是使用IDBConnections和IDBCommands的旧学校方法。

我正在编写一个情况,我将从绑定数据网格中的数据库列出用户表的内容,目的是让用户能够执行标准的CRUD内容。我开始创建一个User类和一个带有相应实现的IUserManager接口。每个用户都被分配到一个部门,当然也需要成为一个在部门上执行CRUD的方法,所以我添加了一个Department类,一个IDepartmentManager接口和一个实现。我进行了设置,以便网格绑定在IUserManager接口上的.GetAll()方法的结果中。然后我开始填写胆量。

我不再在我面前提供代码,但我基本上使用IDBConnection通过IDBCommand使用SQL查询来访问数据存储区。然后我调用了command.ExecuteReader()并迭代了IDataReader对象上的.Read()方法。使用每列的序数我抽出数据,验证它并将其滑入User类并将该类添加到该方法将返回的Dictionary中。所有的DB类当然都是IDisposable,因此将它们包装在一个使用中可以解决这些问题。

非常标准的东西,我已经完成了无数次。

当我意识到我从数据库中取出的部门不是我想在网格中显示的时候。告诉别人这个人在7'并不像说“这个人在会计方面”一样有用。所以我首先玩弄修改我的查询以获取departmentId和name,并将名称存储在用户对象上以便稍后显示。然后我决定给用户一个Department类实例,它将在它的生命周期中挂起来填充。那是我将胆量转换为linq的时候。

   public Dictionary<int, User> GetAll()
    {
        var result = new Dictionary<int, User>();

        using (var datastore = new myEntities())
        {
            result = (from user in datastore.userInfoes
                       join department in datastore.userDepartmentInfoes on user.departmentID equals department.departmentID
                    select new User()
                    {
                        UserIndex = user.id,
                        FirstName = user.firstName,
                        LastName =  user.lastName,
                        Department = new Department()
                        {
                            DepartmentId = user.departmentID.Value,
                            DepartmentName = department.departmentName,
                                                                                                            },
                        Username =  user.userName,
                    }
                ).ToDictionary(x => x.UserIndex, x => x);
        }

        return result;
    }

我开始思考的地方(阅读:可能过度分析)

我的实施工作会很好。对于小型数据集,它甚至可以很好地工作。它甚至适用于较大的数据集(比如10,000)。即使你计算公司里的每个人,我现在工作五次,而你的人数还不到一千人。

但是,如果有一秒钟我为一家拥有1000万员工的大公司工作呢?这将导致departmentName字符串被重复数百万次。

这也让我想到,与IOS的MVC实现不同,这种特殊情况并不是要查询足够多的用户来填充屏幕然后处理分页和内容。一旦调用代码刷新数据绑定,它就会立即将所有1000万用户全部拉回并传回集合。那会很慢。

所以这让我觉得这个方法对于更大的数据集而言既慢又低效。不仅如此,而且可能有200万个“会计”实例。用这个数据集保存它将成为一个主要的记忆力。由于User中的Department类,我们在这里也有点击败了关系数据库的目的。在DB中,您只有一个departmentId int外键引用另一个表中的条目。链接仅在您交叉引用另一个表时发生,即使那时,实际上只有一个&#39; Accounting&#39;任何时候的字符串。在上面的代码中,您将拥有大量的会计核算。漂浮在等待清理的字符串。

MVC场景基本上会知道&#39;它需要X个条目来填充网格的可视区域。它只会从索引Y开始一次查询X,并且当用户导航时,它将根据需要查询和显示其他记录。这比查询所有1000万并且让他们在任何地方闲逛,无论他们是否显示都要好得多。

像我说的那样,我很可能会过度分析这一点。我对linq工作方式的一些假设也可能不正确。但为了学习,我想我不得不问:做这样的事情最好的方法是什么?对于小型数据集,这种事情是否正常?作为一个MCV实现,整个事情会变得更好,而不是将整个数据集拉入网格中吗?

1 个答案:

答案 0 :(得分:0)

如果您需要内存中的整套数据 - 无论如何都必须加载它。我相信你不会在网格中列出10kk用户,对吧?出现的技术是分页。使用示例检查this article from msdn

对于部门对象,您的UserInfo是否有部门的外键?如果是这样,您应该只有userInfo.Department,并且不需要加入。

如果将部门数据绑定到网格列,为什么具有Department属性?我假设您的User类是绑定到UI的东西。将其展平为:

class User
{
 Username 
 UserIndex
 FirstName
 LastName
 DepartmentId
 DepartmentName 
}

GetAll()的目的是什么?您返回一个字典,感觉您需要通过id启用查找。或者您是否使用结果来枚举用户?

对于查找,请考虑与数据库通信,以便在需要时为您提供单个用户数据。如果下一步有意义的话,实现缓存。

对于枚举,不要返回字典 - 即内存中的所有对象,返回IEnumerable与yielding(paged?)结果甚至更好的IQueryable,以便调用GetAll()不会立即执行sql调用,并且调用代码可以通过添加必要的过滤器来调低调用范围