ObjectDataSource未能找到带参数的非泛型方法的的解决以及自定义更新参数的探究
传统ADO.NET的情况下使用ObjectDataSource使得我们可以使用任何数据源作为底层进行CRUD的操作,简单易行。不过ObjectDataSource用的不好往往调试起来很困难(当然这是微软控件封装了太好的缘故:-))。在常见的此类数据源控件出错中,最最常见切频繁出现的就是“XXXDataSource未能找到带参数的……”一类红色的提示。如何解决此类问题呢?
俗话说得好——工欲善其事,必先利其器——我们就从提示入手,仔细研究揣摩ObjectDataSource究竟是如何映射ObjectDataSource的Update方法参数的。
方便期间,我们先创建一个工程(我目前用VS2011 Beta),其中包含一个静态类,用于模拟从数据库中读取的数据资料,以及一个更新方法,大体上代码如下:
[C#]
/// <summary>
/// 实体类
/// </summary>
public class Product
{
public int Id{get;set;}
public string Name{get;set;}
}
public static class DataSourceFile
{
static List<Product> _products = null;
//第一次加载,就初始化仅一次
static DataSourceFile()
{
_products = new List<Product>();
for (int i = 1; i < 11; i++)
{
_products.Add(new Product { Id = i, Name = "Product" + i});
}
}
/// <summary>
/// 选出全部记录的静态方法
/// </summary>
public static IList<Product> GetAllProducts()
{
return _products;
}
public static void UpdateProducts(int Id,string Name)
{
Product up= _products.Find(pro => pro.Id == Id);
up.Name = Name;
}
}
[VB.NET]
''' <summary>
''' 实体类
''' </summary>
Public Class Product
Public Property Id() As Integer
Get
Return m_Id
End Get
Set
m_Id = Value
End Set
End Property
Private m_Id As Integer
Public Property Name() As String
Get
Return m_Name
End Get
Set
m_Name = Value
End Set
End Property
Private m_Name As String
End Class
Public NotInheritable Class DataSourceFile
Private Sub New()
End Sub
Shared _products As List(Of Product) = Nothing
'第一次加载,就初始化仅一次
Shared Sub New()
_products = New List(Of Product)()
For i As Integer = 1 To 10
_products.Add(New Product() With { _
Key .Id = i, _
Key .Name = "Product" & i _
})
Next
End Sub
''' <summary>
''' 选出全部记录的静态方法
''' </summary>
Public Shared Function GetAllProducts() As IList(Of Product)
Return _products
End Function
Public Shared Sub UpdateProducts(Id As Integer,Name As String)
Dim up As Product = _products.Find(Function(pro) pro.Id = Id)
up.Name = Name
End Sub
End Class
对应的aspx代码生成如下:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataSourceID="ProductObjectDataSource" EnableModelValidation="True">
<Columns>
<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />
<asp:BoundField DataField="Id" HeaderText="Id" SortExpression="Id" ReadOnly=true />
<asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductObjectDataSource" runat="server" SelectMethod="GetAllProducts"
TypeName="WebCSharp.DataSourceFile"
UpdateMethod="UpdateProducts">
</asp:ObjectDataSource>
这是最简单的情况,假设现在Id是主键(自然只读不允许修改)的情况下,我们在UpdateProducts这里设置断点,然后F5调试,我们便可以发现p已经填充了更新后的数据,但是Id不会发生变化(回传原来的Id),因此可以进行更新。
不过这里提醒大家注意一点:当把objectDataSource绑定到GridView上的时候,默认生成的<asp:ObjectDataSource……>这里边应该还包括了<UpdateParameters>……</UpdateParameters>一节,其中生成的内容与UpdateProducts参数类型、名称完全一致。这很容给大多数读者造成误会——似乎更新以后的数据就是通过这些Parameters进行传递的,如果你这样认为,那就大错特错了!其实,UpdateParameters只是用于定义额外需要的更新字段,并非是数据模型自身。换句话说,如果你定义了一个UpdateParameter,那么对应的UpdateMethod必须拥有这一个额外的字段(包括类型也必须匹配)!而数据表模型自身的字段即便不定义(当然定义了也无所谓),也会自动传递到UpdateMethod,和其中的参数一一匹配(参数顺序并不重要)。
下面给一个例子进行严格证明(其它地方不变,我只多定义一个UpdateParameter):
<asp:ObjectDataSource ID="ProductObjectDataSource" runat="server" SelectMethod="GetAllProducts"
TypeName="WebCSharp.DataSourceFile"
UpdateMethod="UpdateProducts">
<UpdateParameters>
<asp:Parameter Name="s" Type="string" />
</UpdateParameters>
</asp:ObjectDataSource>
因为多定义了一个参数为string类型的s,因此在对应的UpdateMethod中必然也要有所映射,所以UpdateMethod必须写成:
[C#]
public static void UpdateProducts(int Id,string Name,string s)
{
………………
}
[VB.NET]
Public Shared Sub UpdateProducts(Id As Integer, Name As String, s As String)
…………
End Sub
此时,如果你没有定义s,那么就会引发我说指出的上面错误。
因此我们可以先这样总结:Update方法的参数必须包含由Select语句或者是模型类中的各个公共属性字段作为参数,同时还要包含UpdateParameters中额外定义的参数。更进一步地——如果ObjectDataSource设置了ConflictDection和OldValuesParameterFormatString,那么Update方法必须同时包含这两类参数名称和类型。
Delete方法:未设置“冲突检测”之时,必须指定DataKeyNames(主键作为删除的依据)以及对应的参数名和类型,设置了“冲突检测”之后,则只需指定全部的OldParameterValueFormation的格式化参数名称以及类型即可。
如果上面一个示例使用了“冲突检测”机制,那么Update方法必然要加入OldValuesparameterFormatString(假设设置为old_{0},那么参数就还要多出“int old_Id”、“string old_Name”)。
说了那么半天,你大概感悟颇多,不过同时觉得把字段作为参数一个个写未免有些太过于麻烦。下面我们来介绍一种更为直接的方法——模型实体映射:
所谓“模型实体映射”就是说参数不再是一个个独立的参数那么离散,而是统一扔给一个模型做处理,这样,我们直接就针对类对象处理,而不是一个个单独离散的数据啦(PS:毕竟OOP嘛!)
模型映射分两种情况:
第I型:自映射——
所谓自映射,就是人工无需写任何的代码,直接把一个模型类取代先前的参数即可。那么一开始的例子就直接可以这样表示:
[C#]
public static void UpdateProducts(Product p)
{
Product up= _products.Find(pro => pro.Id == p.Id);
up.Name = p.Name;
}
[VB.NET]
Public Shared Sub UpdateProducts(p As Product)
Dim up As Product = _products.Find(Function(pro) pro.Id = p.Id)
up.Name = p.Name
End Sub
和第一次的例子一样,因为p包含了Id和Name,也被自动映射了。
不过这里注意:如果使用了冲突检测机制,则不能使用此方法(即便为Product定义了OldParameterValueFormation);你只能老老实实定义参数哦!
第II型:自定义实体模型映射:
在第I型的例子中和先前所有的例子中,我们的Id都假设是不改变的。但是现在我们撤去这个理想化的条件,让Id也可以改变,那么如何更新呢?显然,使用只用一个Product只能获取更新后的数据,而我们需要更新前的数据作为参考才可以借助更新前的旧Id来更新旧Id对应的那个模型实体的数据。
你可以选择使用参数化的方式(一个个定义参数),同时设置objectDataSource的ConflictDection和OldParameterValueFormat进行更新。如果我们需要你用OOP的方式呢?也就是定义类似这样的一个Update方法——其中包含一个旧的Product实体和新一个Product实体数据,这样我们直接从List中找到符合条件的旧实体数据,然后逐一把新的值赋给他。
怎么做呢?如果我们直接这样定义:
[C#]
public static void UpdateProducts(OldProduct oldp,Product p)
[VB.NET]
Public Shared Sub UpdateProducts(oldp As OldProduct, p As Product)
显然无论是UpdateParameter或者是自动模型的I方法都无法识别,也会报出本篇抬头的那个错误。
要自定义更新参数,我们还必须了解究竟ObjectDataSource是如何自定义映射的。其实在做映射之前,ObjectDataSource将首先触发Updating事件,把映射的参数名称和对应的数值全部存入一个InputParameters的Dictionary中。因此如果要实现自定义的参数,我们只需在这个方法中逐个取出需要的参数值,并且使用Add方法包含全部UpdateMethod中的参数名称即可。
接着上面的定义,我们给出完整的代码:
[C#]
protected void ProductObjectDataSource_Updating(object sender, ObjectDataSourceMethodEventArgs e)
{
Product p = new Product() { Id = Convert.ToInt32(e.InputParameters["Id"]),Name=e.InputParameters["Name"].ToString() };
OldProduct oldp = new OldProduct() { old_Id = Convert.ToInt32(e.InputParameters["old_Id"]), old_Name = e.InputParameters["old_Name"].ToString() };
e.InputParameters.Clear();
e.InputParameters.Add("p", p); //Product类型,名称p(对应UpdateMethod方法"p"名称)
e.InputParameters.Add("oldp", oldp); //OldProduct类型,名称oldp(对应UpdateMethod方法"oldp"名称)
}
[VB.NET]
Protected Sub ProductObjectDataSource_Updating(sender As Object, e As ObjectDataSourceMethodEventArgs)
Dim p As New Product() With { _
Key .Id = Convert.ToInt32(e.InputParameters("Id")), _
Key .Name = e.InputParameters("Name").ToString() _
}
Dim oldp As New OldProduct() With { _
Key .old_Id = Convert.ToInt32(e.InputParameters("old_Id")), _
Key .old_Name = e.InputParameters("old_Name").ToString() _
}
e.InputParameters.Clear()
e.InputParameters.Add("p", p) 'Product类型,名称p(对应UpdateMethod方法"p"名称)
e.InputParameters.Add("oldp", oldp) 'OldProduct类型,名称oldp(对应UpdateMethod方法"oldp"名称)
End Sub