Keeping the codebase down is one of my considerations here. For non-nullable fields, this method eliminates both (1) stored procedure and (1) business layer function.

This is a typical accessor I use. Here I'm getting/setting the Active bit in a Users table. It takes in a Record ID, and a bit value: true/false for the set(), null for the get().

The return value is either the existing or newly set value. (I chose to always return the eventual value)

Optionally, (or as the default), the return value can be set as null if, e.g., the record doesn't exist.


if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[p_Users_Active]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[p_Users_Active]
GO

CREATE PROCEDURE [dbo].p_Users_Active
	 @UserID int
	,@Active bit = null
AS

	DECLARE @bret bit = 0;

	-- check for set()
	IF ( @Active IS NOT NULL )
	BEGIN
		UPDATE
			Users
		SET
			Active = @Active
		WHERE
			ID = @UserID;
	END

	-- always return the value
	SELECT
		@bret = Active
	FROM
		Users
	WHERE
		ID = @UserID;


	-- for nullable return, use:
	-- SELECT @bret;

	SELECT IsNull(@bret, Cast(0 As bit));

GO

You'll certainly have occasion to use nullable values, (most often DateTime values in my case), but let's consider a nullable bit field.

Why would you need these? Aren't most bit fields a simple true/false with a default value?

In my experience: YES - most of them.

Consider a Yes/No radio-button that you require to be answered, rather than an opt-in checkbox. Maybe a "Do you want email from us" kind of thing that you want someone to definitely answer.

Now, with a single-page form with a [Submit] button, this really would be irrelevant if the form is for a new user to your website/portal. After all, if the user doesn't yet exist, neither does their data record, so there's no need for a "get()" at all.

But, what about a multi-page form that allows users to save and later return? Here we would want a nullable bit field, so, if unanswered, the Yes/No doesn't get populated upon returning to the form.

This would be a case for, e.g., p_Users_EmailAllow_Get() & p_Users_EmailAllow_Set() routines, or whatever your naming conventions are.

If unanswered, the get() would return null upon return, and not select either of the radio buttons.

Similar to the Stored Procedure, this function can optionally return null as well. (commented code for this situation).
I've adopted the double-underscore, "__" prefix for single-column accessors so they'll stand out in IntelliSense.

// __Active
// ~or~ public static bool? __Active(int UserID, bool? Active)
public static bool __Active(int UserID, bool? Active)
{
	// ~or~ bool? bret = null;
	bool bret = false;

	ConnectionManager cm = new ConnectionManager();
	SqlCommand cmd = new SqlCommand();

	try
	{
		SqlConnection cn = cm.OpenConnection();

		cmd.Connection  = cn;
		cmd.CommandType = CommandType.StoredProcedure;

		cmd.CommandText = "p_Users_Active";

		cmd.Parameters.AddWithValue("@UserID", UserID);

		if (Active.HasValue)
			cmd.Parameters.AddWithValue("@Active", Active);

		// ~or~ 
		// object obj = cmd.ExecuteScalar();
		// if ( obj != DBNull.Value )
		//	   bret =  bool.Parse(obj.ToString());

		bret = (bool)cmd.ExecuteScalar();

	}
	catch (Exception ex)
	{
		throw (ex);
	}
	finally
	{
		cm.CloseConnection();
	}

	return bret;
}


We start with the simple HTML declaration (in ASP.Net). The <span> element will be explained below.

<div class="form-group col-12 mt-3">
	<asp:CheckBox CssClass="fw-bold form-check" runat="server" ID="chkActive" Text="Active" />
	<span class="fsmActive" style="display:none" />
</div>

Since there's an AJAX call going on, we need access to the Record ID (in this case, a UserID). This value is loaded in the code behind.

<input type="hidden" runat="server" id="hdnUserID" value="0" />

We also hook in the JavaScript for the onChange() event the code behind along with the needed parameters..
You can do this one right inside the <asp:CheckBox> server control, but I prefer to do this in the code-behind.
And, yes, JQuery can be used to get the ClientID - but in external JavaScript files or User Controls, this method may not work.

string jsActive = "javascript:return fnOnActiveChanged(" + userId + ", '" + chkActive.ClientID + "');";
chkActive.Attributes.Add("OnChange", jsActive);

Now the actual JavaScript and its callback:
This can utilize AJAX script, but I choose to use PageMethods() since they're all wired in for .NET. Anyway, it all comes down to AJAX anyway.
Also included is the CSS class for the fade in/out return message. (the fsm prefix means "fading status message")

.fsmActive {
	color:forestgreen;
	font-weight:bold;
	font-size:0.9em;
}

function fnOnActiveChanged(userId, idChk) {

	var checked = document.getElementById(idChk).checked;

	PageMethods.wmOnActiveChanged(parseInt(userId, 10), checked, cbOnActiveChanged);

	return false;
}

function cbOnActiveChanged(msg) {
	$('.fsmActive').text(msg);
	$('.fsmActive').fadeIn(800).delay(1200).fadeOut(800);
}

And the Web Method:

[System.Web.Services.WebMethod()]
[System.Web.Script.Services.ScriptMethod()]
public static string wmOnActiveChanged(int id, bool active)
{
	Guid key = BenSawyer.Configuration.WebServiceKey;
	BenSawyerWS.ServiceMain service = Misc.GetWebServiceConnection();

	string res = "OK";

	service.Users_Active(key, id, active);

	return res;
}