惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
腾讯CDC
博客园 - 聂微东
爱范儿
爱范儿
罗磊的独立博客
P
Proofpoint News Feed
博客园 - Franky
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
酷 壳 – CoolShell
酷 壳 – CoolShell
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 司徒正美
美团技术团队
MongoDB | Blog
MongoDB | Blog
WordPress大学
WordPress大学
A
About on SuperTechFans
I
InfoQ
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Help Net Security
Microsoft Azure Blog
Microsoft Azure Blog
G
Google Developers Blog

博客园 - Insus.NET

Vue3实现拖放式上传 拖放式上传 实现MultipartStreamProvider经Web API上传文档或图片 文件上传和表单字段混合提交 在HttpContext.Current返回null时如何操作 angularjs根据类型动态加载组件 angularjs获取数据服务 扫一扫直连WiFi二维码 User Profile Service 服务未能登录 Visual Studio2026创建Vue项目 安装与配置node.js HTTP Error 403.14 - Forbidden VisualStudio2026回滚上一版本 消息认证码(加强) 网站无法使用插值字符串语法 HMAC(Hash-based Message Authentication Code)认证示例 浏览器自动发送域凭据 企业内小网站兼用Windows验证登录 访问用户控件的函数 onblur事件改为监听处理 将警报消息改为吐司消息 混合式提高用户编辑与操作效率 光标离开文框后即刻更新 WebForm实现Web API JavaScript对GridView删除行后并重新给其数据绑定 把CS值传给JS使用 v2 确认信息confirm由C#后端移至javascript前端 无法发布网站Web Site JavaScript判断字符是否为decimal 点击单元格弹出窗口处理数据返回父页
内容有无变化OnBlur即时更新引起的问题与解决
Insus.NET · 2026-04-30 · via 博客园 - Insus.NET

文本框中onblur事件,实现用户输入完成后,光标离开文本框实现即时更新。
但是,用户只要用鼠标点击文本框离开后,它均会执行更新操作,文本框内容是否有变化都会进行。

这是一种不好的性能影响。

先示例,然后看看Insus.NET怎样去解决它的。
数据还是使用下面随笔中的数据表,存储过程等。为了能看到效果,在数据表中添加一个字段,UpdatedDate来记录更新时间。
修改表:

ALTER TABLE [dbo].[ZTestData]
ADD [UpdatedDate] DATETIME NOT NULL DEFAULT(GETDATE())
GO

View Code

修改更新的存储过程:

ALTER PROCEDURE [dbo].[usp_ZTestData_UpdateBeiZhu]
@ID INT,
@BeiZhu NVARCHAR(30)
AS
BEGIN
    UPDATE [dbo].[ZTestData]
    SET
        [BeiZhu] = @BeiZhu,
        [UpdatedDate] = GETDATE()
    WHERE 
        [ID] = @ID
END
GO

View Code

修改获取记录的存储过程:

ALTER PROCEDURE [dbo].[usp_ZTestData_SelectAll]
AS
BEGIN
    SELECT 
        [ID],
        [IMT_pz],
        [IMT_gx],
        [BeiZhu],
        [UpdatedDate]
    FROM [dbo].[ZTestData]
END
GO

View Code

下面是修改App_Code下的ZTestData.cs类:
2026-04-29_16-40-45

此篇随笔,演示示例,由GridView改为html table呈现,
2026-04-29_16-45-20

<form id="form1" runat="server">
    <div>
        <table>
            <thead>
                <tr>
                    <td>ID</td>
                    <td>Remarks</td>
                    <td>Update Time</td>
                </tr>
            </thead>
            <tbody >
                <tr>
                    <td>
                        <%# Eval("ID") %>
                        <input id="HiddenID" type="hidden" value=' <%# Eval("ID") %>' />
                    </td>
                    <td>
                        <input id="TextBeiZhu" type="text" value='<%# Eval("BeiZhu") %>' onblur="update(event);" />
                    </td>
                    <td>
                        <%# Eval("UpdatedDate") %>
                    </td>
                </tr>
            </tbody>
            <tfoot>
            </tfoot>
        </table>
    </div>
    <asp:Button ID="ButtonRefreshData" runat="server" Text="RefreshData" Style="display:none" OnClick="ButtonRefreshData_Click" />
</form>

View Code

cs后端对html table数据绑定,
2026-04-29_16-53-27

public object DataItem { get; set; }

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        Data_Binding();
    }

}

private void Data_Binding()
{
    ZTestDataEntity ztde = new ZTestDataEntity();
    DataView dv = ztde.Get_DataTable_GetData().DefaultView;
    dv.RowFilter = "ID = 2"; //指定记录主键,只是为了演示。
    DataItem = dv.ToTable().DefaultView[0];
    DataBind();
}

View Code

宣告一个对象object(第1个红色感叹号)。
第2处注意的地方,ztde.Get_DataTable_GetData()返回的是DataTable。

把它转为DefaultView后,可以对从数据库得到数据进行RowFilter过滤。过滤条件你可以自定。
还有一行是dv.ToTable().DefaultView[0];
给它一个0索引。受到过滤条件影响,有时也许返回不一定是1条记录。

还有一个隐藏服务控件Button按钮OnClick事件,
2026-04-29_17-08-37


最后是html input的text的 onblur="update(event);"事件,
照搬随笔最初参考随笔的链接的写法(不过仍有一些细节修改而变化):
2026-04-29_17-16-00

 <script src="Scripts/axios.min.js"></script>
 <script type="text/javascript">

     //onblur事件
     function update(event) {
         updateBeizhuByTextBox(event.target)
             .catch(console.warn);
     }

     function updateBeizhuByTextBox(txtboxElement) {
         return new Promise((resolve, reject) => {

             let url = '/api/MyTest/UpdateBeiZhu';
             var postData = {
                 ID: HiddenID.value,
                 BeiZhu: txtboxElement.value.trim()
             };

             axios.post(url, JSON.stringify(postData), {
                 headers: { 'Content-Type': 'application/json; charset=utf-8' }
             })
                 .then(response => {
                     if (!response.data.Status) {
                         alert(response.data.Message);
                         reject(response.data.Message);
                     } else {
                         alert(response.data.Message);
                         resolve(response.data);
                     }
                 })
                 .catch(error => {
                     console.error(error);
                     reject(error);
                 })
                 .finally(() => {
                     refreshData();
                 });
         });
     }

     function refreshData() {
         document.getElementById('<%= ButtonRefreshData.ClientID %>').click();
     }


 </script>

View Code


在#95行代码是为刷新呈现的数表而已。

看看效果(关注Update time栏位时间变化):
update_data_byonblur


从效果中看到,内容无变化,它更新了。
如何解决它呢?Insus.NET想到2种方法:
从数据端入手,写个INSTEAD OF UPDATE触发器,
2026-04-30_09-02-56

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        leo yeung
-- Create date: 2026-04-30
-- Description:    更新备注前验证是否有变化
-- =============================================
CREATE TRIGGER [dbo].[tri_ZTestData_UpdateBeiZhu]
ON  [dbo].[ZTestData]
INSTEAD OF UPDATE 
AS 
BEGIN    
    SET NOCOUNT ON;

    IF NOT EXISTS
    (
        SELECT 1 FROM inserted i 
        INNER JOIN deleted d ON (i.[ID] = d.[ID] AND i.[BeiZhu] = d.[BeiZhu]
    ))
    BEGIN        
        UPDATE target
        SET
            [IMT_pz] = i.[IMT_pz],
            [IMT_gx] = i.[IMT_gx],
            [BeiZhu] = i.[BeiZhu],
            [UpdatedDate] =i.[UpdatedDate]
        FROM [dbo].[ZTestData] target
        INNER JOIN inserted i ON target.[ID] = i.[ID];        
    END
    ELSE
    BEGIN
        RAISERROR(N'备注内容无任何变化,更新略过。',16,1)
    END
END
GO

View Code

如果不想写触发器,那就改更新存储过程,
2026-04-30_09-22-13

ALTER PROCEDURE [dbo].[usp_ZTestData_UpdateBeiZhu]
    @ID INT,
    @BeiZhu NVARCHAR(30)
AS
BEGIN
    IF NOT EXISTS
    (
        SELECT 1 FROM [dbo].[ZTestData]
        WHERE 
            [ID] = @ID
            AND[BeiZhu] = @BeiZhu
    )
    BEGIN    
        UPDATE [dbo].[ZTestData]
        SET
            [BeiZhu] = @BeiZhu,
            [UpdatedDate] = GETDATE()
        WHERE 
            [ID] = @ID
    END
    ELSE
    BEGIN
        RAISERROR(N'备注内容无任何变化,更新略过。',16,1)
        RETURN
    END
END
GO

View Code

上面数据库改正,它只是让更新是否继续。性能还是不容乐观,因为网络连接还是要连,数据sql语句还是要执行一遍。

不防改前端,参考如下,
2026-04-30_10-33-44

 window.lastSavedData = window.lastSavedData || new Map();

 function updateBeizhuByTextBox(txtboxElement) {
     return new Promise((resolve, reject) => {       

         var uniqueKey = txtboxElement.id + '_' + HiddenID.value.trim();                
         var newValue = txtboxElement.value.trim();

         // 检查是否与上次成功保存的值相同
         var lastValue = window.lastSavedData.get(uniqueKey);
         if (lastValue === newValue) {
             console.log("内容未改变,无需保存", uniqueKey, newValue);
             return resolve("内容未改变");
         }

         let url = '/api/IMT/UpdateBeiZhu';
         var postData = {
             ID: HiddenID.value,
             BeiZhu: newValue
         };

         axios.post(url, JSON.stringify(postData), {
             headers: { 'Content-Type': 'application/json; charset=utf-8' }
         })
             .then(response => {
                 if (response.data.Status !== true && response.data.Status !== "true") {
                     alert(response.data.Message);
                     reject(response.data.Message);
                 } else {
                     alert(response.data.Message);
                     window.lastSavedData.set(uniqueKey, newValue);
                     console.log("已记录", uniqueKey, newValue);
                     resolve(response.data);
                 }
             })
             .catch(error => {
                 console.error(error);
                 alert("保存失败,请重试!", true);
                 reject(error);
             })
             .finally(() => {
                 //
             });
     });
 }       

View Code

这样一改,可以防止同一文本框第2次更新时,不会去呼叫Web API把数据上传至数据库进行更新。
虽然不是很完善,已经做出很大改善了。

是否有更好方案?期待......