• Featured post

C# Async ops

El nucleo de la programacion asincrona son los objetos Task y Task<T>. Ambos son compatibles con las palabras clave async y await.

Primero hay que identificar si el codigo se usa para trabajos enlazados a I/O o si estos son CPU intensivos.

  • Si el codigo espera algo como una BBDD o una response de un servidor, es codigo I/O. En este caso hay que usar async y await
  • Si el codigo realiza un calculo costoso, es CPU intensivo. Use await para esperar una operacion que se ha comenzado en background con Task.run

Read More

PgSQL Functions (Stored Procedure)

(This is an implementation example. For an explanation on this, please check my other post: SQL Triggers & Stored Procedures)

Function example

-- example of function that triggers when an entry is inserted into a table
--   and manages this data inserting data as needed in another table
CREATE OR REPLACE FUNCTION your_schema.my_function_name(arg1 character varying, data character varying)
	RETURNS character varying
	LANGUAGE plpgsql
AS $function$
BEGIN

-- check if already exists in the other table
IF
	(SELECT COUNT(*) FROM your_schema.other_table WHERE name=arg1) > 0) THEN
		RETURN 'Error: 1210. data already exists';
END IF;

-- insert and manage data
INSERT INTO your_schema.other_table (name, data) VALUES (arg1, data);
RETURN 'Success: 1200';

END $function$;

How to Debug in DBeaver

There are two options, logs or break the function with an exception.

If logs are enough, you just write the message to output per console.

RAISE NOTICE 'this is null';

To see logs in DBeaver click here. Then you may execute the function to see the logs.

how to debug in dbeaver

If you want to break the function runtime with an exception, you write the following instead

RAISE EXCEPTION SQLSTATE '90001' USING MESSAGE = 'error. this already exists';

SQL Views & Materialized Views

SQL View

A view in SQL is essentially a virtual table. It doesn’t store data physically. Instead it presents data from one or more underlying tables through a predefined SQL query. Think of it as a saved query that you can treat like a table.

  • Views don’t hold data themselves. When you query a view, the database executes the underlying query to fetch data in real-time.
  • Views can simplify complex queries by encapsulating them. Instead of writing a complex JOIN or subquery each time, you select from the view.
  • Views can restrict user access to specific rows or columns, enhancing security by exposing only necessary data.
  • Since views are generated on the fly, they always reflect the current state.
CREATE OR REPLACE VIEW active_customers AS
SELECT customer_id, name, email
FROM customers
WHERE status = 'active';

When to use a view

  • Use it to simplify complex queries that you use frequently and you need the most current data every time.
  • Restrict user access to specific data by exposing only certain columns or rows through a view

Materialized View

A materialized view is like a regular view, but it stores the query result’s phisically and it doesn’t involve executing the underlying query each time.

  • Since data is precomputed and stored, querying a materialized view is faster for complex queries over large datasets.
  • Because of this, data in a materialized view can become outdated and needs to be refreshed periodically.
CREATE OR REPLACE MATERIALIZED VIEW sales_summary AS
SELECT product_id, SUM(quantity) AS total_quantity
FROM sales
GROUP BY product_id;

When to use a materialized view

  • Is ideal for speeding up complex queries that are resource-intensive and slow to execute.
  • Suitable for scenarios where data doesn’t change frequently and fast read performance is needed.
  • You need to tolerate data that’s not always up-to-date

SQL Triggers & Stored Procedures

SQL Triggers

A SQL trigger is a code block that executes automatically when a specified event occurs on a table or view, such as an insert, update or delete.

A trigger can be used to perform actions before or after the event such as checking data integrity or spread data changes between tables.

Such an example would be to create a trigger that prevents users from inserting or updating data in a table if the data violates a rule, such as a maximum length or a required field.

CREATE TRIGGER trigger_name
BEFORE/AFTER event
ON table_name
FOR EACH ROW
BEGIN
	-- trigger code or call to procedure
END;

SQL Stored Procedures

A SQL Procedure is a code block that performs one or more tasks and can be called by other programs or queries. A procedure can accept parameters, return values and use variables, loops and conditional statements.

A procedure can be used to encapsulate complex logic or reuse code.

CREATE PROCEDURE procedure_name (parameters)
BEGIN
	-- procedure code
END;

Best practices

  • Use descriptive and consistent names.
  • Document your code with comments and explain the purpose and logic
  • Avoid using too many or complex procedures or functions. This may affect performance or reliability of your database. They really increase difficulty to follow an operation.

Reference(s)

https://www.linkedin.com/advice/3/how-do-you-use-sql-triggers-procedures-functions?lang=en

C# Raw string literals

Before raw string literals

The main problems with long string literals were:

  • strings that include double quotes tend to be unreadable
  • identation looks messy in multiline strings

we have the following json we want to put into a string literal

{
	"number": 42,
	"text": "Hello, world",
	"nested": { "flag": true}
}

an ordinary quoted string literal looks like this:

string json = "{\r\n  \"number\": 42,\r\n  \"text\": \"Hello, world\",\r\n  \"nested\": { \"flag\": true }\r\n}"

verbatim string literals work slightly better here but will be missaligned when used over nested code. Also quotes look different as they still need to be scaped.

foreach(var item in list)
{
	if(Check(item))
	{
		string json = @"{
  ""number"": 42,
  ""text"": ""Hello, world"",
  ""nested"": { ""flag"": true }
}";
	}
}

Using raw string literals

Here’s how this example looks using raw literals.

foreach(var item in list)
{
	if(Check(item))
	{
		string json = """
		{
			"number": 42,
			"text": "Hello, world",
			"nested": { "flag": true }
		}
		""";
	}
}

Inside raw literals we don’t need to scape chars. Also we’re able to indent our code.

Read More

C# Anonymous methods and lambda expressions

Named method vs anonymous method

A named method is a method that can be called by its name:

// named method declaration
public string Join(string s1, string s2)
{
	return s1+s2;
}
// named method usage
var result = Join("this is ", "a joined string");

An anonymous method is a method that is passed as an argument to a function, without the need for its name. These methods can be constructed at runtime or be evaluated from a lambda expression.

// declaration
public void ProcessBook(Action<Book> process, List<Book> books)
{
	foreach (Book b in books)
	{
		process(b);
	}
}
// usage - print book titles
ProcessBook(bookList, book => Console.WriteLine(book.Title))

Here book => Console.WriteLine(book.Title) is the lambda expression and it’s result is an anonymous function that will be run by the method ProcessBook

Read More

C# Partial classes

A partial class in c# can be used to split functionality across multiple files, each with the same namespace and name.

A good use case for them is to use them as a stepping-stone in refactoring god-classes. If a class has multiple responsibilities (really large files), it may be a TEMPORAL way to refactor & split behaviours, before splitting them into different classes.

Usually, you can’t have 2 classes with the same name. This is unless you mark them as partial.

// this works fine, although it's not the best use case
public partial class MyClass
{
	public bool Ok { get; set; }
}

public partial class MyClass
{
	public bool IsOk()
	{
		return Ok;
	}
}

When you have multiple partial classes the compiler will merge them all into one single class.
Some rules:

C# Pattern matching

Overview of scenarios where you can use pattern matching. These techniques can improve the readability and correctness of your code.

Reduce nested if-else

I have a complex nested if-else statements scenario

// DON'T DO THIS
if (order.Status == "Pending")
{
	ProcessPendingOrder(order);
} else if (order.Status == "Completed")
{
	ProcessCompletedOrder(order);
} else if (order.Status == "Cancelled")
{
	ProcessCancelledOrder(order);
} else
{
	throw new InvalidOperationException("Unknown status");
}

instead do this

order.Status switch
{
	"Pending" => ProcessPendingOrder(order),
	"Completed" => ProcessCompletedOrder(order),
	"Cancelled" => ProcessCancelledOrder(order),
	_ => throw new InvalidOperationException("Unknown status")
};

Using it as a method

You can test a variables to find a match on specific values

public State PerformOperation(string command) =>
	command switch
	{
		"SystemTest" => RunDiagnostics(),
		"Start" => StartSystem(),
		"Stop" => StopSystem(),
		_ => throw new ArgumentException("Invalid string value", nameof(command)),
	};

_ is the discard pattern that matches all values. It handles any error conditions where the value doesn’t match one of the defined values.

Read More

C# Expression bodied members

Expression body definitions let you provide a member’s implementation in a concise, readable form.

Methods

Expression-bodied method consists of a single expression that returns:

  • a value whose type matches the return value
  • or performs an operation for void methods
public class Person(string firstName, string lastName)
{
	public override string ToString() => $"{firstName} {lastName}".Trim();
	public void DisplayName() => Console.WriteLine(ToString());
}

Read-only properties

You can use them to implement read-only property.

public class Location(string locationName)
{
	public string Name => locationName;
}

Reference(s)

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members

C# Collections' index and range operators

Index operator ^1

The index operator is used to reach the last positions of an array or list.

before

List<int> arr = [0, 1, 2, 3, 4, 5];
if(arr[arr.Count-1] == 5)
	Console.WriteLine("match!"); // prints

same but using index operator

List<int> arr = [0, 1, 2, 3, 4, 5];
if(arr[^1] == 5)
	Console.WriteLine("match!"); // prints

we can use it to reach any position starting from the back

List<int> arr = [0, 1, 2, 3, 4, 5];
if(arr[^2] == 4)
	Console.WriteLine("match!"); // prints

Read More

Deconstruction in ASP.NET Core

Desconstructors allows us to extract in a single expression, properties of an object or elements of a tuple, and assign them to distinct variables.

record and record struct automatically provide a Deconstruct method.

Classes

before deconstruction we had to manually extract each variable

var pat = new Person() { Name = "Patrick", BirdDate = new DateOnly(1999, 1, 18)};
var name = pat.Name;
var birthDate = pat.BirthDate;
Console.WriteLine($"Name: {name}, birthdate: {birthDate}");

class Person

public class Person
{
	public string Name { get; set; }
	public string Location { get; set; }
}

with deconstruction

var pat = new Person() { Name = "Patrick", BirdDate = new DateOnly(1999, 1, 18)};
var (name, birthDate) = pat;
Console.WriteLine($"Name: {name}, birthdate: {birthDate}");

class Person with Deconstructor

public class Person
{
	public string Name { get; set; }
	public string Location { get; set; }
	
	public void Deconstruct(out string name, out string location) 
	{
		name = Name;
		location = Location;
	}
}

if you don’t want all properties, just discard some using the discard character _

var pat = new Person() { Name = "Patrick", BirdDate = new DateOnly(1999, 1, 18)};
var (name, _) = pat;
Console.WriteLine($"Name: {name}");

Tuples

The same applies to tuples

// tuple creation
(string name, string location) person = ("mario", "spain");
// deconstructor
var (name, location) = person;

Reference(s)

https://blog.ndepend.com/deconstruction-in-c/