Code

(pause to look at some Content Secuity Policy issues I had neglected)
...OK, with that taken care of for the moment, lets look at an IP Address table and some code to block specific countries and/or IP Addresses.
Here's the table:
Using my code-generator, let's look at a few snippets:
The Insert stored procedure:

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

CREATE PROCEDURE p_IPAddress_Insert
/******************************************************************************
 *  file:   p_IPAddress_Insert.sql
 *  date:   2024.02.08 T 17:03:08
******************************************************************************/

	 @Id              int	 OUTPUT
	,@IpAddress       varchar(32)
	,@CountryCode     char(2)
	,@City            varchar(60)
	,@State           varchar(60)
	,@Postal          varchar(32)
	,@Latitude        decimal(7,4)
	,@Longitude       decimal(7,4)
AS

	IF NOT EXISTS (
		SELECT
			Id
		FROM
			IPAddress
		WHERE
			IpAddress = @IpAddress )
		BEGIN

			-- when dealing with addresses, make sure we handle overflows...
			SET @City   = SUBSTRING(@City,   1, 60)
			SET @State  = SUBSTRING(@State,  1, 60)
			SET @Postal = SUBSTRING(@Postal, 1, 32)

			INSERT INTO IPAddress(
				 IpAddress
				,CountryCode
				,City
				,State
				,Postal
				,Latitude
				,Longitude
			)
			VALUES(
				 @IpAddress
				,@CountryCode
				,@City
				,@State
				,@Postal
				,@Latitude
				,@Longitude
			);
	
			IF @@error = 0
				SET @Id = SCOPE_IDENTITY();
			ELSE
				SET @Id = -1;
		END
	ELSE
		BEGIN
			SELECT
				@Id = Id
			FROM
				IPAddress
			WHERE
				IpAddress = @IpAddress;
		END

GO

Some business class sections
Private variables...

#region Class Level Private Variables

private int        _Id              = 0;
private string     _IpAddress       = "";
private string     _CountryCode     = "";
private bool       _Blocked         = false;
private int        _BlockedHitCount = 0;
private string     _City            = "";
private string     _State           = "";
private string     _Postal          = "";
private double     _Latitude        = 0;
private double     _Longitude       = 0;

#endregion //Class Level Private Variables

Some properties...

#region Properties

/// public accessor for _Id
public int Id
{
	get { return _Id;}
	set
	{
		if (value != _Id)
		{
			_Id = value;
		}
	}
}
/// public accessor for _IpAddress
public string IpAddress
{
	get { return _IpAddress;}
	set
	{
		if (value != _IpAddress)
		{
			_IpAddress = value;
		}
	}
}

// etc.

#endregion //Properties

A DataReader helper...

// Load RecordSet
public void FromReader(SafeDataReader dr)
{
	int i = 0;

	_Id              = dr.GetInt32(i++);
	_IpAddress       = dr.GetString(i++);
	_CountryCode     = dr.GetString(i++);
	_Blocked         = dr.GetBoolean(i++);
	_BlockedHitCount = dr.GetInt32(i++);
	_City            = dr.GetString(i++);
	_State           = dr.GetString(i++);
	_Postal          = dr.GetString(i++);
	_Latitude        = dr.GetDouble(i++);
	_Longitude       = dr.GetDouble(i++);
}


...and one of the functions:

// GetByID
public static IPAddress GetByID(int Id)
{
	// return
	IPAddress iPAddress = null;

	ConnectionManager cm = new ConnectionManager();
	SqlCommand cmd = new SqlCommand();
			
	try
	{
		SqlConnection cn  = cm.OpenConnection();
		SafeDataReader dr = null;
		try
		{
			cmd.Connection  = cn;
			cmd.CommandType = CommandType.StoredProcedure;
					
			cmd.CommandText = "p_IPAddress_GetByID";

			cmd.Parameters.AddWithValue("@Id", Id); 
					
			dr = new SafeDataReader(cmd.ExecuteReader());
			if ( dr.Read() )
			{
				iPAddress = new IPAddress();
				iPAddress.FromReader(dr);
			}
		}
		finally
		{
			if ( dr != null && !dr.IsClosed )
				dr.Close();
		}
	}
	catch(Exception ex)
	{
		throw(ex);
	}
	finally
	{
		cm.CloseConnection();
	}

	return iPAddress;
}

On this subject of Business Layer functions, I've move to some frequent "single-column" accessors in my code. Sometimes this is because there's just a single bit-field (like Active/Enabled) that needs updating, and sometimes because traversing the entirety of the existing (ancient) code base is something I'd rather not do.  Example here.