Lambdas, and Take While, and Group By

[原文作者]Amanda Silver, Paul Vick, Scott Wisniewski

[原文链接]Lambdas, and Take While, and Group By, Oh My!

 

太棒了,Visual Basic 2008 Beta2版本将于今天发布 ,即将回归到我们用户所热爱的桌面上,这个自然的栖息地。(请大家注意在登陆界面上GPM 微笑的脸哦,— Beta 版本的完成使他格外的开心。)

新版本到底有什么新玩意呢?加入了比之前Beta1版本更多的新特性,能否列举一些呢?这里有个关于Beta2 版本VB Language详细的清单:

  • Query 操作符:  Group By, Group Join, Take (While), Skip [While], Aggregate, Count, Sum, Min, Max, Average, From->Let
  • Nullable 支持
  • Lambda 表达式(aka inline Funcitons)
  • Partial 方法ScottGu 前几天提起过,ScottWis前几周也发表过一些这方面的资料。
  • Anonymous 类型 w/Keys-这个PaulV已经开始讨论这些了。

这里是一些查询语句的例子,好了你现在就可以开始用VB写代码了!

 

‘ Find suppliers in the same city as customers

From cust In db.Customers _

Join sup In db.Suppliers _

On cust.City Equals sup.City _

Select cust.CompanyName, sup.ContactName, cust.City

 

‘ Find suppliers in the same city, with the same postalcode as customers

From cust In db.Customers _

Join sup In db.Suppliers _

On cust.City Equals sup.City And cust.PostalCode Equals sup.PostalCode

Select cust.CompanyName, sup.ContactName, cust.City

‘ Find the average unit price and count for products by category

From prod In db.Products _

Group By prod.CategoryID _

Into Average(prod.UnitPrice), Count()

 

‘ For each customer find the group of the suppliers in the same country

From cust In db.Customers _

Group Join sup In db.Suppliers _

On cust.Country Equals sup.Country _

Into SuppliersInCountry = Group _

Select cust.CompanyName, SuppliersInCountry

‘ For each customer, the count of the number of suppliers in the same country

From cust In db.Customers _

Group Join sup In db.Suppliers _

On cust.Country Equals sup.Country _

Into NumSuppliers = Count() _

Select cust.CompanyName, NumSuppliers

 

‘ Avg price, avg # in stock, and max on order for non-discontinued products

Aggregate prod In db.Products _

Where Not prod.Discontinued _

Into AvgPrice = Average(prod.UnitPrice), _

     AvgInStock = Average(prod.UnitsInStock), _

     MaxOnOrder = Max(prod.UnitsOnOrder)

‘ Find orderID, OrderDate, and OrderTotal for each order

From order In db.Orders _

Where order.OrderDate > #7/22/1996# _

Aggregate ordDet In order.Order_Details _

Into OrderTotal = _

       Sum(ordDet.UnitPrice *ordDet.Quantity * (1 – ordDet.Discount))

Select order.OrderID, order.OrderDate, OrderTotal

 

‘ Fill a dictionary with the customers by CompanyName

db.Customers.ToDictionary(Function(cust As Customer) cust.CompanyName)

 

 

另外为了支持新增的语言环境,我们现在提供了关键字的智能感知。

 

 

我们有关于XML的智能感知,难以置信吗?对,我们做到了。

 

 

 

而且我们大大改善了后台编译的性能,如果你对这个新版本有任何意见建议请告诉我们吧(如果你在beta1上再安装beta2或是在Vs2005上装beta2,你可以看看Scott的文章,他有提到如何避免AJAX 的扩展问题)

还有不要错过VB  9Linq其他令人激动的新特性啊,举个例子来说, linq 现在支持Like操作符了! 还有新的ASP:LinqDataSource 控件。

还等什么呢快来领略J

将VS2005的项目转换到VS2008—使 LINQ能够使用

[原文作者]: Benth Massi

[原文链接]: Converting VS 2005 Projects to VS 2008 – Enabling LINQ

           假如现在你有一个用Visual Studio 2005 开发的应用程序你想进一步挖掘和转换它,并开始使用VS2008中的 LINQ功能。那么在这里,我打算简单介绍下使LINQ正常工作, 你需要什么步骤, 这些步骤也同样取决于你需要用到LINQ的什么功能。

          Visual Studio 2008 中,有多种的对象特性使你能够用.NET 2.0, 3.0 或者3.5框架来在2008的环境中编写程序。Scott Gu的文章 Rick Strahl对此都有介绍。这就意味着你不需要为了正常运行先前版本的程序而去安装多个版本的Visual Studio IDE在你的电脑上(注意,但是如果你先前用.NET 1.0 1.1开发的,那么还是需要安装20022003IDE的)。这是个好消息, 因为它不但节省了磁盘空间和省去了前后的转换,还能够使你获得更多的有利条件像调试和编辑而不需要冒着升级项目文件的风险。但是如果你希望把项目升级成3.5的版本去使用LINQ功能,你需要自己添加一些新的命名空间。

         当你第一次在Visual Studio 2008 中打开一个Visual Studio 2005的项目时,系统会提示升级你的项目。实际上这样做是为了升级了你的项目文件(.vbproj) 和解决方案 (.sln) 使其和2008兼容。而这个项目仍然可以用2005打开,它是向下兼容的。但是解决方案文件是单一的,因此如果你是个团队开发项目并且混合有20052008 两种版本IDE时,你需要保留两个解决方案,不过幸运的是,你的项目文件(比解决方案要有更多的变化)是可以共享的。

        如今所有的这些升级过程所做的是同时升级项目文件和解决方案,你的程序仍然以.NET2.0的平台为对象,为了升级你的程序去使用新的特性比如LINQ功能,你需要去改变到需要的Framework版本和添加一些新的引用。你也希望根据那些特性打开新的推断特性的选项,这允许编译器通过估计右值表达式去推测出局部变量的申明类型。这将对编写LINQ的查询非常有用。为了支持它,在解决方案资源管理器中双击我的项目打开项目属性,选择编译标签,选择下方的“On”按钮。

        现在去改变目标Framework,点击“Advanced Compile Options…”

        选择Framework 3.5,按ok,项目就会被关闭后重新开启。如果你再次打开项目看引用标签你会发现System.Core.dll 3.5的版本被自动的引用了。但是为了能使用LINQ,还需要引入两个命名空间和一些和LINQ有关的引用,为了完全支持Linq to Objects,需要添加 System.Linq的引用。现在你可以编写查询对象的代码如下

Dim currentFiles = From File In My.Computer.FileSystem.GetFiles(CurDir) _

                   Select My.Computer.FileSystem.GetFileInfo(File)

 

为了能编写出作用于DataSet数据集的查询,你需要添加System.Data.DataSetExtensions的引用, 之后你要重新运行与你想编写的LINQ程序相关的DataSet的生成器。右键DataSet 选“Run Custom Tool”,  如此会重新产生DataSet 编码,这个编码可以使DataTables  继承位于System.Data.DataSetExtension 命名空间里 的一个叫做TypedTableBaseLINQ-ready 的类。  接下来你就可以在已经被定义的DataSet上编写程序了,例如:

 

Dim total = Aggregate Products In Me.CategoryProductDataSet.Products _

            Where Products.CategoryID = 1 AndAlso _

                  Products.Discontinued = False _

            Into Sum(Products.UnitPrice * Products.UnitsInStock)          

        如果你要开始使用LINQ XML 支持,你需要添加向System.Xml.Linq.dll的引用以及加入 System.Xml.Linq 命名空间,之后你就可以编写作用于xml的语句了 如:

 

Dim survey = XElement.Load(CurDir() & “\questions.xml”)

 

Dim questions = From q In survey…<question> Select q

 

        最后,如果你想要在你最新升级的项目中使用LINQ to SQL,那也是相当简单的。只要右键选择添加新的项目并选择“LINQ to SQL”类模板,它将会开启一个新的O/R设计器并且自动的为你添加System.Data.Linq.dll的引用。这允许你编写类似下面的查询(依靠SQL-server):

 

Dim countryList = From Customer In Db.Customers _

                  Where Customer.Country <> “” _

                  Order By Customer.Country _

                  Select Customer.Country Distinct

        希望我已经清楚的呈现了怎么把你当前的项目中转换到VS2008使用LINQ的第一步, 那么你还等什么呢。

 

另一种方式使用LINQ

[原文作者]Beth Massi

[原文链接]Another Way to LINQ

 

我曾经在ScottGu博客上读过他的部分文章,是关于在ASP.NETLinqDatasource数据源如何使用LINQ to SQL的。文章浅显易懂,想必Scott为此花了不少的心思如果你之前没有读过他的一些文章那么你最好花点时间去读一读,这会对你非常有好处的。而且他在例子中分别使用了VBC#代码。下面我要着重介绍的一个例子是关于使用Lambda表达式得到每样产品的总价

  Dim products = From p In db.Products _

                 Where p.Category.CategoryName.StartsWith(“C”) _

                 Select p.ProductID, _

                   p.ProductName, _

                   p.UnitPrice, _

                   NumOrders = p.Order_Details.Count, _

                   Revenue = p.Order_Details.Sum(Function(details) _

                             details.UnitPrice * details.Quantity)

 

在这里,我们所使用的lambda用关键字”Function”来标识,它被用在了这个叫”Sum”的扩展方法里,其目的是完成对每样产品总价的计算。在VB 9里面我们也可以使用“Aggregate”来完成同样的功能。

另外,我们可以在Where子句中使用”Like”,这种用法和上面的作用是相同的:

 

Dim products = From p In db.Products _

               Where p.Category.CategoryName Like “C*” _

               Select p.ProductID, _

                p.ProductName, _

                p.UnitPrice, _

                NumOrders = p.Order_Details.Count, _

                Revenue = _

                   Aggregate detail In p.Order_Details _

                   Into Sum(detail.UnitPrice * detail.Quantity)

VB 9 提供了很多其他的比较通用的LINQ表达式,这样你就省去了手动再写一遍的时间,例如求和Sum, 取最小值Min, 最大值Max, 取平均Average等等。

我个人比较喜欢那些容易理解的VB 表达式句法,因此我更倾向于使用这些已有的。

如果你希望了解更多的有关Query的信息,我们为你准备了一些视频

 

希望大家能够从中发现乐趣!

元数据编程与LinQ

[原文作者]: Benth Massi

[原文链接]: Metadata Programming and LINQ

    提到LinQ,我最喜欢的应用之一是用她查询元数据(Metadata)以编写灵活的程序。或者反过来,用LinQ迅速地创建自己的object的元数据。基本上,元数据就是“描述数据的数据(data about data),比如描述内容的信息。在这里我想通过一个简单的例子来展示如何应用数据库里的信息描述窗体上的控件【即查询并应用元数据】;然后,我会创建一些描述这些控件信息的XML数据【即创建元数据】。注意:这个例子是用Visual Studio 2008 Beta2版本创建的。

下面是这个具体的例子:现在,我想根据所选的语言从数据库得到控件的文本信息,这些文本会根据语言种类的不同而不同。我已经在数据库里创建了一个非常简单表格:ControlData,它有三个字段:Name, Description Language,如下:

Name       Description    Language

———- ————-  ———

Button1    Hello          English 

Button2    Goodbye        English 

Button3    I love VB      English 

Button4    LINQ Rocks!    English 

TextBox1   Hello          English 

TextBox2   Goodbye        English 

Button1    Buon Giorno    Italian 

Button2    Ciao           Italian 

Button3    Amo VB         Italian 

Button4    LINQ è potente Italian 

TextBox1   Buon Giorno    Italian 

 

接下来我创建了一个包含若干控件的Form。有些控件的名字和上面数据库的Name字段是一致的,有些不然。我们要做的是写一些查询语句以找出和数据库里Name字段匹配的控件,并根据所选的语言(由Combobox选择)改变其显示文本。它看起来像这样:

接下来的第一件事是创建一个LINQ to SQL class, 我们用O\R designer来实现这个操作。只需在你的Solution Explorer里右击project【在这个例子里是“LINQMetaData1”】-> Add New Item-> 选择“LINQ to SQL Class”模板。这样会生成一个.dbml文件并打开O/R designer(我用了默认的名字DataClasses1.dbml)。你可以把所需的表从Server Explorer里直接拖进来,然后它会像DataSet designer一样帮你生成所有的代码。

在这个例子里,我把ControlData 表拖到O/R designer上,这会自动创建一个DataContext,它有一个叫做ControlData的类。现在我们就可以用LinQ了,ControlData会帮我们把LinQ转化为SQL。【你可以在Server Explorer里点击“Show All Files”,点开“DataClasses1.dbml”,打开“DataClasses1.designer.vb”查看背后生成的代码。】下面我们只需创建一个DataContext的实例并查询ControlData

我第一个要查询的是Combobox需要显示的Language信息。可以用distinct关键字把信息查询出来并把它设为ComboBox1DataSource:

Public Class Form1

    Private db As New DataClasses1DataContext

 

    Private Sub Form1_Load() Handles MyBase.Load

 

        Dim choices = From info In db.ControlDatas _

                      Select info.Language Distinct

 

        Me.ComboBox1.DataSource = choices

    End Sub

End Class

 

‘choices’变量得到的将是stringlist(IQueryable(Of String)),把它设为ComboboxDataSource毫无问题 我们最终得到的是两个string“English” “Italian”

然后我们来搞定ComboBoxSelectedIndexChanged事件 - 目的是让各个控件的文本随之改变(我们这里只去设置Button的文本)。我们可以利用Me.Controls这个集合来找出所有顶层(top-level)的button,这个集合包含的类型是ControlsCollection,我们需要将它转化成Button类型:

Dim buttons = From c In Me.Controls _

              Where TypeOf c Is Button _

             Select CType(c, Button)

 

愿意的话,我们现在就可以遍历这个buttonlist(IEnumerable(Of Button))并设置它们的Text属性:

For Each button In buttons

    button.Text = “LINQ is cool”

Next

然而我们想做的是找出这个list中与ControlData对象的Name字段相匹配的项,并根据语言来设置相应的文字,所以SelectedIndexChangedevent handler的完整代码应该是这样:

Private Sub ComboBox1_SelectedIndexChanged() Handles ComboBox1.SelectedIndexChanged

        Dim buttons = From c In Me.Controls _

                    Where TypeOf c Is Button _

                    Select CType(c, Button)

 

        Dim buttonInfo = From Button In buttons _

                         From info In db.ControlDatas _

                         Where info.Name = Button.Name _

                            AndAlso info.Language = Me.ComboBox1.SelectedValue.ToString _

                         Select Button, info.Description

 

        For Each item In buttonInfo

            item.Button.Text = item.Description

        Next

End Sub

请注意上面上面代码里的buttonInfo,如果你注意到我们Select的是Buttoninfo.Description,你就应该意识到buttonInfo是一个“匿名类型(anonymous type)” 的list【匿名类型,简单的说就是编译器生成的临时类型,这个类型不需要用户自己声明,但是可以像用一般的类型一样存取对象的各个字段】当我们使用它的时候,编译器会为我们创建一个匿名类型的list。 如果我们选择语言,button的文本就会改变:

最后一件事情是创建元数据,我要创建一个简单的XML文件以存储前面的form和数据库的元数据。这可以用LINQ to XML XML literals技术来实现。很简单,唯一的小把戏是我们需要使用group join, 这是因为有些控件不在数据库中,并且一些控件有多行信息。我们有零个或多个ControlData对象与每个控件相匹配【因为有多个Language】,我用group join来将这些描述查询出来,并作为子查询(sub-query)放到<Text>元素里,然后将<Text>放到<Control>元素的最后,而select出来的<Control>元素被放到了根节点<Form>的下面:

Private Sub cmdMakeXML_Click() Handles cmdMakeXML.Click

 

        Dim formData = <?xml version=1.0?>

                       <Form

                           name=<%= Me.Name %>

                           height=<%= Me.Height %>

                           width=<%= Me.Width %>

                           top=<%= Me.Top %>

                           left=<%= Me.Left %>

                           font=<%= Me.Font %>>

                           <%= _  

                               From item In Me.Controls _

                               Let c = CType(item, Control) _

                               Group Join info In db.ControlDatas On info.Name Equals c.Name Into Group _

                               Order By c.Name _

                               Select <Control

                                          type=<%= c.GetType %>

                                          name=<%= c.Name %>

                                          height=<%= c.Height %>

                                          width=<%= c.Width %>

                                          top=<%= c.Top %>

                                          left=<%= c.Left %>

                                          font=<%= c.Font %>>

                                          <%= From info In Group _

                                              Order By info.Language _

                                              Select <Text language=<%= info.Language %>>

                                                         <%= info.Description %>

                                                     </Text> %>

                                      </Control> _

                           %></Form>

 

        My.Computer.FileSystem.WriteAllText(“Form1.xml”, formData.ToString, False)

        Process.Start(“notepad.exe”, “Form1.xml”)

 

End Sub

下面是生成的XML文件:

LinQ真的很强大,她为我们简化了很多常见的操作,比如从多个form里查询数据,数据库/对象集合/XML操作等。我附上了刚才的例子,但是请注意如果你用的不是Visual Studio 2008 Beta 2版本的话,有些特性可能不会正常工作【你可能还需要重新做一遍添加LINQ to SQL class的工作(在Solution Explorer里删除DataClasses1.dbml,重新添加,拖拽数据表)】。如果想学习更多关于LinQ的知识,请查看MSDNLinQ Project

相关程序:   http://blogs.msdn.com/bethmassi/attachment/4037074.ashx

Enjoy!

多窗体之间数据处理

多窗体之间数据处理

[原文作者]: Beth Massi

[原文链接]: Using Data Across Multiple Windows Forms

 

  最近我遇到很多关于多窗体之间数据交互处理的问题,如果你已经看过我的Forms over Data系列课程,你应该了解到:怎样建立一个database连接它保存你的数据以及在代码中操作数据。这篇文章是对那些课程的扩展,这里我将利用一个示例描述怎样基于一个DataSet创建多窗体程序,我们将利用Northwind数据库中的Catagories表和Products表作为示例。

  根据具体情况,实际上有很多方法可以将多个窗体连接到同一个DataSet上,这里我将用一个比较典型的例子来说明一下:从一个表格窗体中显示可编辑的详细信息窗体,如下图所示:

 

首先建立一个工程,将Form1窗体(也就是主窗体)Text属性设置为Catagories and Products

通过数据源窗口新建一个Data Source对象;选择SQL Server Database File,数据库为Northwind.mdf,选择Categories Products两个表,将DataSet名称设为CategoriesProductsDataSet

完成以后会有一个CategoriesProductsDataSet.XSD被创建(参见One-to-Many video)。当DataSet创建完成后,将CategoriesProductsDataSetCategories Products两个表从数据源窗口拖拽到Form1窗体中(注意:ProductsCategories下面的节点)

此时主窗体中的两个DataGridView将分别显示Categories和其相关的Products信息,窗口底部将会创建两个BindingSource,并且它们被绑定到了相应GridDataSource属性中,与此同时,有一个BindingNavigator工具条出现在窗体上。现在我们想要做的是创建第二个窗体以编辑产品的详细信息。

我首先对Categories 表和 Products表的TableNewRow事件处理方法添加了两行代码,以便使数据处理更简单,在这个事件处理方法中我只是对一些非空字段设置了默认值。如要打开DataSet的后台代码进行编辑,只要用鼠标右键点击Solution_Explorer窗口中的CategoriesProductsDataSet.XSD文件,接着选择“View Code”就可以进入代码编辑窗口,在这里你还可以对一些字段进行简单验证,就像我在video on adding validation中演示的那样。

Partial Class CategoriesProductsDataSet

    Partial Class CategoriesDataTable

 

        Private Sub CategoriesDataTable_TableNewRow(ByVal sender As Object, _

                    ByVal e As System.Data.DataTableNewRowEventArgs) _

Handles Me.TableNewRow

 

            ‘Set defaults for non-nullable fields

            Dim category As CategoriesRow = CType(e.Row, CategoriesRow)

            category.CategoryName = “New Category”

        End Sub

    End Class

 

    Partial Class ProductsDataTable

 

        Private Sub ProductsDataTable_TableNewRow(ByVal sender As Object, _

ByVal e As System.Data.DataTableNewRowEventArgs) _

Handles Me.TableNewRow

 

            ‘Set defaults for non-nullable fields

            Dim product As ProductsRow = CType(e.Row, ProductsRow)

            product.ProductName = “New Product”

            product.Discontinued = False

        End Sub

    End Class

 

End Class

 

接着我添加了一个新窗体Form2到工程中(也就是详细信息窗体,用来编辑产品的详细信息),将Form2Text属性设置为Products Detail

将数据源窗口中Products的显示属性设为details,并将它拖拽到Form2中,这次用不到BindingNavigator工具条,将它删除,并在窗体底部添加两个按钮:OKCancel

 

然后删除下面的ProductsTableAdapter组件,打开Form2窗体的后台代码,在Form.Load事件处理方法中删除编辑器自动生成的Fill代码,因为我们不需要重新从数据库中填充DataSet,而是将数据从主窗体传递到详细信息窗体中。

此时,传递数据最简单的方式是在详细信息窗体的后台代码中创建一个新的构造函数,将主窗体上我们正在编辑的CategoriesProductsDataSet和打算编辑的Product行的主键作为参数。这样我们就可以在详细信息窗口中设置ProductBindingSourceDataSource  Filter 属性值, 以使它显示选中的行,从而保持主窗体和详细信息窗体同步。在详细信息窗体后台代码中类定义的下方输入“Sub New”, 并按回车键来自动生成正确的构造函数调用,接着修改它的签名和设置ProductBindingSource属性。

 

Sub New(ByVal ds As CategoriesProductsDataSet, ByVal id As Integer)

 

    ‘ This call is required by the Windows Form Designer.

    InitializeComponent()

    ‘ Add any initialization after the InitializeComponent() call.

 

    ‘ Set the DataSource of the BindingSource and then set the Filter

      so that the correct row will be displayed on the detail form.

    Me.ProductsBindingSource.DataSource = ds

    Me.ProductsBindingSource.Filter = “ProductID = “ & id.ToString

End Sub

 

回到主窗体中,在工具条上添加一个按钮,并把他的Text属性设为Edit Product Detail,双击按钮进入事件处理方法,加入如下代码:

 

Private Sub ToolStripButton1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) _

Handles ToolStripButton1.Click

 

    Me.ProductsBindingSource.EndEdit()

 

    If Me.ProductsBindingSource.Position > -1 Then

        ‘Get the current product row

        Dim row As CategoriesProductsDataSet.ProductsRow

        row = CType(CType(Me.ProductsBindingSource.Current, _

DataRowView).Row, CategoriesProductsDataSet.ProductsRow)

 

        ‘Open the product detail form passing the dataset and the product ID

        Dim frm As New Form2(Me.CategoriesProductsDataSet, row.ProductID)

        frm.Show()

    End If

End Sub

 

此方法中,首先调用了ProductsBindingSource.EndEdit(),这样在打开详细信息窗体编辑Product之前,所有在主窗体中所做的改变被保存到DataSet中。将ProductBindingSource.Current属性赋值给ProductRow来获取当前Product行,并将ProductID和主窗体中的CategoriesProductsDataSet传递给详细信息窗体的构造函数。

这里我允许用户打开任意数量的详细信息窗体,但是如果你想一次只打开一个窗体,只需要将frm.Show()修改为frm.ShowDialog();这个例子演示了编辑当前行,如果没有任何行被选中,将不会弹出窗体;这种情况下,如果你想在详细信息窗体被打开之前自动添加一新行,可以在EndEdit()之前调用ProductBindingSource.AddNew()(提示:如果这样做,那么在DataTable partial class中像我前面所描述的那样,对非空字段设置默认值是非常重要的)。

最后对按钮OKCancelForm2 Close添加事件处理方法,以确认是否保存对Product所做的修改。代码如下:

 

 

Private Sub Form2_FormClosing(ByVal sender As Object, _

ByVal e As System.Windows.Forms.FormClosingEventArgs) _

Handles Me.FormClosing

 

    Me.ProductsBindingSource.CancelEdit()

End Sub

 

Private Sub cmdCancel_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles cmdCancel.Click

 

    Me.ProductsBindingSource.CancelEdit()

    Me.Close()

End Sub

 

Private Sub cmdOK_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles cmdOK.Click

 

    Me.ProductsBindingSource.EndEdit()

    Me.Close()

End Sub

 

我已附加了一个完整的基于Northwind数据库的代码示例,里面也包括了我在这个视频这篇文章中演示的怎样恰当地保存相关的DataTables

记住,要在窗体中很好地对数据进行处理,关键是使用BindSource对象,这个类使数据处理和窗体控制变的非常简单。更多信息请参见Understanding Data video

 

附件: MultiFormSample.zip