Xamarin.Forms经用户定义表类型插入含varbinary字段DataTable至SQL Server失败
问题核心原因
你WinForm直连数据库能成功,是因为直接把内存中原生、类型完全匹配的DataTable传给SqlClient,不存在序列化/反序列化的类型转换问题。走Web API接口失败的核心原因有两个:
- 直接传DataTable作为接口入参兼容性极差:你在Xamarin端把DataTable序列化成JSON时,byte[]类型的
profilePic会被转成Base64字符串,Web API端默认反序列化DataTable时,不会自动把这个Base64字符串转回byte[],最终反序列化出来的DataTable里profilePic列是字符串类型,和SQL要求的varbinary类型不匹配,自然插入失败。 - 你之前给结构化参数没指定
TypeName,如果列顺序/类型和数据库自定义表类型不完全对齐,也会触发插入错误。你去掉profilePic字段后剩下的都是字符串类型,反序列化不会出错,所以能跑通。
修复方案
按以下步骤修改即可解决问题:
- 第一步:废弃直接传DataTable的做法,定义强类型传输对象,从根源避免序列化类型不匹配问题
public class StdDetailDto { public string id { get; set; } public string section { get; set; } public byte[] profilePic { get; set; } }
- 第二步:修改Xamarin端发送逻辑,不要序列化DataTable,直接传强类型列表
async private void hf_Clicked(object sender, EventArgs e) { urlClass urldata = new urlClass(); string uri = urldata.url + "/PostStdDetails"; byte[] bytess = new byte[] { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100 }; // 替换原来构造DataTable的逻辑,直接构造强类型列表 var postData = new List<StdDetailDto> { new StdDetailDto{ id = "STDID0010", section = "A", profilePic = bytess }, new StdDetailDto{ id = "STDID0011", section = "B", profilePic = bytess } }; StringContent content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); try { HttpResponseMessage responsepost = await client.PostAsync(uri, content); if (responsepost.IsSuccessStatusCode == true) { await DisplayAlert("Section Added", "Section per Grade is added successfully", "OK"); } else { // 读取接口返回的具体错误内容,方便排查问题 var errMsg = await responsepost.Content.ReadAsStringAsync(); await DisplayAlert("Operation Failed", $"Response Failed! {errMsg}", "Cancel"); } } catch (System.Net.WebException exp) { bool ans = await DisplayAlert("Connection Failed", "Please Check Your Internet Connection!", "Retry", "Cancel"); if (ans == true) hf_Clicked(sender, e); } catch (Exception exp) { bool ans = await DisplayAlert("Connection Failed", "Lost Connection!", "Retry", "Cancel"); if (ans == true) hf_Clicked(sender, e); } }
- 第三步:修改Web API端的Post方法,入参接收强类型列表,手动在服务端构造和数据库自定义表类型完全匹配的DataTable,结构化参数必须指定TypeName
public void PostStdDetails([FromBody] List<StdDetailDto> stddetails) { // 手动构造DataTable,列顺序、列名、列类型必须和数据库中dbo.StdDetailsTypeTable定义完全一致 DataTable dt = new DataTable(); dt.Columns.Add("id", typeof(string)); dt.Columns.Add("section", typeof(string)); dt.Columns.Add("profilePic", typeof(byte[])); foreach (var item in stddetails) { dt.Rows.Add(item.id, item.section, item.profilePic); } // 用using自动释放资源,不需要手动写Close using (SqlConnection conn = new SqlConnection(DBConnection)) { conn.Open(); using (SqlCommand cmd = new SqlCommand("getstddetails", conn)) { cmd.CommandType = CommandType.StoredProcedure; SqlParameter tvpParam = cmd.Parameters.Add("@stdDetailsTable", SqlDbType.Structured); // 必须指定TypeName,和数据库自定义类型完全对应 tvpParam.TypeName = "dbo.StdDetailsTypeTable"; tvpParam.Value = dt; cmd.ExecuteNonQuery(); } } }
- 第四步:给Web API方法加异常捕获和日志,不要只返回通用失败信息,把具体异常信息记录下来,后续排错不需要反复试错。
注意:你贴的WinForm测试代码里第三列名写的是
profile,和自定义表类型的profilePic不一致,能跑通应该是贴代码时的笔误,实际代码里列名、列顺序必须和数据库定义完全一致,差一个字符都可能报错。
内容的提问来源于stack exchange,提问作者rana hd
相关产品推荐
相关产品推荐

