Data StructureDimensionsDescription
Series11D labeled homogeneous array, sizeimmutable.
Data Frames2General 2D labeled, size-mutable tabular structure with potentially heterogeneously typed columns.
Panel3General 3D labeled, size-mutable array.

create data frame

data = {
  "calories": [420, 380, 390],
  "duration": [50, 40, 45]

#load data into a DataFrame object:
df = pd.DataFrame(data)

#change columns names
df.columns = ['x','y','z']

#index rows instead of numbers
df = pd.DataFrame(data, index = ["day1", "day2", "day3"])

#read csv
df = pd.read_csv('data.csv')

#read json 
df = pd.read_json('data.json')

a = [1, 7, 2]
myvar = pd.Series(a, index = ["x", "y", "z"])

basic properties and functions

1axes Returns a list of the row axis labels
2dtype Returns the dtype of the object.
3empty Returns True if series is empty.
4ndim Returns the number of dimensions of the underlying data, by definition 1.
5size Returns the number of elements in the underlying data.
6values Returns the Series as ndarray.
7head() Returns the first n rows.
8tail() Returns the last n rows.
9columns:[] to edit or get columns

read data frame

1.loc()Label based
2.iloc()Integer based
3.ix()Both Label and Integer based
#by row index
print(df.loc[[0, 1]])
df =pd.DataFrame({'good':[1,2,3],'bad':[4,None,6]})
print(df.loc[1,"bad"])#nan object
print(df.iloc[0:2, 0:2])#all data frame
df = pd.DataFrame(np.random.randn(8, 4), columns = ['A', 'B', 'C', 'D'])

# Integer slicing
print df.ix[:4]
print df.ix[:,'A']


#iterate by columns then inside Series(Values)
df.columns = ['x','y','z']
for col in df:
    for val in df[col]:

#iterate by rows
for row_index,row in df.iterrows():
   print row_index,row

#iterate by tuples
for index,*values in df.itertuples():


#index sort
import pandas as pd
import numpy as np

unsorted_df = pd.DataFrame(np.random.randn(10,2),index=[1,4,6,2,3,5,9,8,0,7],colu
   mns = ['col2','col1'])

print sorted_df

#values sort
import pandas as pd
import numpy as np

unsorted_df = pd.DataFrame({'col1':[2,1,1,1],'col2':[1,3,2,4]})
   sorted_df = unsorted_df.sort_values(by=['col1','col2'])

print sorted_df

edit data frame

df=pd.DataFrame({'A': [1, 2, 3, 4, 5],"B":[1, 2, 3, 4, 5]})
df['B']=df['B'].apply(lambda x: math.pow(x,2))
df["B"]=[[math.sqrt(x),x/2] for x in df["B"]]


  • left − A DataFrame object.
  • right − Another DataFrame object.
  • on − Columns (names) to join on. Must be found in both the left and right DataFrame objects.
  • left_on − Columns from the left DataFrame to use as keys. Can either be column names or arrays with length equal to the length of the DataFrame.
  • right_on − Columns from the right DataFrame to use as keys. Can either be column names or arrays with length equal to the length of the DataFrame.
  • left_index − If True, use the index (row labels) from the left DataFrame as its join key(s). In case of a DataFrame with a MultiIndex (hierarchical), the number of levels must match the number of join keys from the right DataFrame.
  • right_index − Same usage as left_index for the right DataFrame.
  • how − One of ‘left’, ‘right’, ‘outer’, ‘inner’. Defaults to inner. Each method has been described below.
  • sort − Sort the result DataFrame by the join keys in lexicographical order. Defaults to True, setting to False will improve the performance substantially in many cases.
import pandas as pd
left = pd.DataFrame({
   'Name': ['Alex', 'Amy', 'Allen', 'Alice', 'Ayoung'],
right = pd.DataFrame(
   'Name': ['Billy', 'Brian', 'Bran', 'Bryce', 'Betty'],


one = pd.DataFrame({
   'Name': ['Alex', 'Amy', 'Allen', 'Alice', 'Ayoung'],

two = pd.DataFrame({
   'Name': ['Billy', 'Brian', 'Bran', 'Bryce', 'Betty'],
print pd.concat([one,two],keys=['x','y'],ignore_index=True)

cut continues values to classes

df = pd.DataFrame({'number': [1,2,3,4,5,6,7,8,9,10,11]})
df['bins'] = pd.cut(df['number'], (0, 5, 8, 11), 
                    labels=['low', 'medium', 'high'])

oop in python

magic functions

class tt:
    def __init__(self,value):
        self.text = value
    def __str__(self):#print(obj)
        return self.text
    def __repr__(self):#print(obj) return obj.text
        return self.text
    def __add__(self, other):#obj+other return obj.text+other.text
        return self.text+other.text
    def __len__(self):#len(obj)  return len(obj.text)
        return len(self.text)
    def __getattr__(self, item):#obj.item return item
        return item
    def __getitem__(self, item):#obj[item] return item
        return item
    def __call__(self, *args, **kwargs):#obj() return obj.text
        return self.text
    def __eq__(self, other):#obj==other return obj.text==other.text
        return self.text==other.text
    def __ne__(self, other):#obj!=other return obj.text!=other.text
        return self.text!=other.text
    def __iter__(self):#for i in obj: return obj.text
        return iter(self.text)
class A:
    def __getitem__(self, item):
        if(isinstance(item, slice)):

a = A()

generic class

from typing import TypeVar, Generic

T = TypeVar('T',int , float,complex,decimal.Decimal)
class Stack(Generic[T]):
    def __init__(self) -> None:
        # Create an empty list with items of type T
        self.items: list[T] = []

    def push(self, item: T) -> None:

    def pop(self) -> T:
        return self.items.pop()

    def empty(self) -> bool:
        return not self.items

predefined static properties

  • __dict__ − Dictionary containing the class’s namespace.
  • __doc__ − Class documentation string or none, if undefined.
  • __name__ − Class name.
  • __module__ − Module name in which the class is defined. This attribute is “__main__” in interactive mode.
  • __bases__ − A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list.
class Employee:
   def __init__(self, name="Bhavana", age=24): = name
      self.age = age
   def displayEmployee(self):
      print ("Name : ",, ", age: ", self.age)

print ("Employee.__doc__:", Employee.__doc__)
print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__ )


from abc import ABC, abstractmethod
class demo(ABC):
   def method1(self):
      print ("abstract method")
   def method2(self):
      print ("concrete method")

access modifier

class Employee:
   def __init__(self, name, age, salary): = name # public variable
      self.__age = age # private variable
      self._salary = salary # protected variable
   def displayEmployee(self):
      print ("Name : ",, ", age: ", self.__age, ", salary: ", self._salary)

e1=Employee("Bhavana", 24, 10000)

print (
print (e1._salary)
print (e1.__age)


from enum import Enum

class subjects(Enum):
   ENGLISH = "E"
   MATHS = "M"
obj = subjects.SANSKRIT
print (type(obj),, obj.value)#<enum 'subjects'> SANSKRIT S

from enum import Enum, unique

class subjects(Enum):
   ENGLISH = 1
   MATHS = 2
   SANSKRIT = 2#error value duplicated


class test:
obj = test()
print (type(obj))#<class '__main__.test'>

print (isinstance(10, int))#true
print (isinstance(2.56, float))#true
print (isinstance(2+3j, complex))#true
print (isinstance("Hello World", str))#true

def test():
print (callable("Hello"))
print (callable(abs))#true
print (callable(list.clear([1,2])))
print (callable(test))#true

class test:
   def __init__(self): = "Manav"
obj = test()
print (getattr(obj, "name"))

class test:
   def __init__(self): = "Manav"
obj = test()
setattr(obj, "age", 20)
setattr(obj, "name", "Madhav")
print (, obj.age)

class test:
   def __init__(self): = "Manav"
obj = test()
print (hasattr(obj, "age"))
print (hasattr(obj, "name"))

dir(object) # get list of attributes belong to the object

static methods

class A:
    value: int=14 #static variable you can access it by A.value
    def bar():#static method

other way to call method

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def mm(self):
        return self.x, self.y,2))
print(fun)  # (1, 2)

setter and getter


from datetime import date

class Employee:
    def __init__(self, name, birth_date, start_date): = name
        self.birth_date = birth_date
        self.start_date = start_date

    def name(self):
        return self._name

    def name(self, value):
        self._name = value.upper()

    def birth_date(self):
        return self._birth_date

    def birth_date(self, value):
        self._birth_date = date.fromisoformat(value)

    def start_date(self):
        return self._start_date

    def start_date(self, value):
        self._start_date = date.fromisoformat(value)

collections and iterations

  • Set: Its unique feature is that items are either members or not. This means duplicates are ignored:
  • Mutable set: The set collection
  • Immutable set: The frozenset collection
  • Sequence: Its unique feature is that items are provided with an index position:
  • Mutable sequence: The list collection
  • Immutable sequence: The tuple collection
  • Mapping: Its unique feature is that each item has a key that refers to a value:
  • Mutable mapping: The dict collection.
  • Immutable mapping: Interestingly, there’s no built-in frozen mapping.
  • [:]: The start and stop are implied. The expression S[:] will create a copy of sequence S.
  • [:stop]: This makes a new list from the beginning to just before the stop value.
  • [start:]: This makes a new list from the given start to the end of the sequence.
  • [start:stop]: This picks a sublist, starting from the start index and stopping just before the stop index. Python works with half-open intervals. The start is included, while the end is not included.
  • [::step]: The start and stop are implied and include the entire sequence. The step—generally not equal to one—means we’ll skip through the list from the start using the step. For a given
  • [start::step]: The start is given, but the stop is implied. The idea is that the start is an offset, and the step applies to that offset. For a given start, a, step, s, and a list of size |L|.
  • [:stop:step]: This is used to prevent processing the last few items in a list. Since the step is given, processing begins with element zero.
  • [start:stop:step]: This will pick elements from a subset of the sequence. Items prior to start and at or after stop will not be used.
a = slice(1, 2, 3)#[start:stop:step]


list1 = [1, 2, 3, 4, 5]
list2 = [6, 7, 8, 9, 10]
print(list1 + list2)  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list1 * 2)  # [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
list1.append(6)  # [1, 2, 3, 4, 5, 6]
list1.extend([7, 3, 3])  # [1, 2, 3, 4, 5, 6, 7, 3, 3]
del list1[0]  # [2, 3, 4, 5, 6, 7, 3, 3]
list1.remove(3)  # [2, 4, 5, 6, 7, 3, 3]
list1.pop(0)  # [4, 5, 6, 7, 3, 3]
list1.insert(0, 1)  # [1, 4, 5, 6, 7, 3, 3]
list1.sort()  # [1, 3, 3, 4, 5, 6, 7]
list1.sort(reverse=True)  # [7, 6, 5, 4, 3, 3, 1]
list1.sort(key=lambda x: x % 2)  # [6, 4, 7, 5, 3, 3, 1]
list1.reverse()  # [1, 3, 3, 4, 5, 6, 7]
print(list1.count(3))  # 2
list1.clear()  # []
list1 = [1, 2, 3, 4, 5]
list2 = list1.copy()
#unpack the list
print(*arr)# print(1,2,3,4,5,6,7,8,9,10)
print(ls)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


Only a number, string or tuple can be used as key. All of them are immutable. You can use an object of any type as the value.

dict[key]Extract/assign the value mapped with keyprint (d1[‘b’]) retrieves 4d1[‘b’] = ‘Z’ assigns new value to key ‘b’
dict1|dict2Union of two dictionary objects, retuning new objectd3=d1|d2 ; print (d3){‘a’: 2, ‘b’: 4, ‘c’: 30, ‘a1’: 20, ‘b1’: 40, ‘c1’: 60}
dict1|=dict2Augmented dictionary union operatord1|=d2; print (d1){‘a’: 2, ‘b’: 4, ‘c’: 30, ‘a1’: 20, ‘b1’: 40, ‘c1’: 60}
d1=dict([('a', 100), ('b', 200)])
d2 = dict((('a', 'one'), ('b', 'two')))
d3=dict(a= 100, b=200)
d4 = dict(a='one', b='two')

update dictionary

marks = {"Savita":67, "Imtiaz":88, "Laxman":91, "David":49}
print ("marks dictionary before update: \n", marks)
marks1 = {"Sharad": 51, "Mushtaq": 61, "Laxman": 89}
print (marks)#{'Savita': 67, 'Imtiaz': 88, 'Laxman': 89, 'David': 49, 'Sharad': 51, 'Mushtaq': 61}

d1.update(k1=v1, k2=v2)

unpack dictionary

marks = {"Savita":67, "Imtiaz":88, "Laxman":91, "David":49}
marks1 = {"Sharad": 51, "Mushtaq": 61, "Laxman": 89}
newmarks = {**marks, **marks1}#{'Savita': 67, 'Imtiaz': 88, 'Laxman': 89, 'David': 49, 'Sharad': 51, 'Mushtaq': 61}
a,*b=[1,2,3]#1 [2, 3]

delete and remove

del dict['key']
val = dict.pop(key)
1dict.clear()Removes all elements of dictionary dict.
2dict.copy()Returns a shallow copy of dictionary dict.
3dict.fromkeys()Create a new dictionary with keys from seq and values set to value.
4dict.get(key, default=None)For key key, returns value or default if key not in dictionary.
5dict.has_key(key)Returns true if a given key is available in the dictionary, otherwise it returns a false.
6dict.items()Returns a list of dict’s (key, value) tuple pairs.
7dict.keys()Returns list of dictionary dict’s keys.
8dict.pop()Removes the element with specified key from the collection
9dict.popitem()Removes the last inserted key-value pair
10dict.setdefault(key, default=None)Similar to get(), but will set dict[key]=default if key is not already in dict.
11dict.update(dict2)Adds dictionary dict2’s key-values pairs to dict.
12dict.values()Returns list of dictionary dict’s values.


for else statement

for i in arr:
    if i>3:
else: #executes if the loop completes without a break
T1 = (10,20,30,40)
T2 = ('one', 'two', 'three', 'four')
L1, L2 = list(T1), list(T2)
L3 = tuple(y for x in [L1, L2] for y in x)#(10, 20, 30, 40, 'one', 'two', 'three', 'four')
for m in filter(lambda x: isinstance(x, str), mm):
for m,v in vars(tt("a")).items():#vars return all attributes of object
even= [i for i in ls if i%2==0]#only even numbers

builtin functions

numbers = [1.0, 1.5, 2.0, 2.5]
result_1 = map(lambda x: x.is_integer(), numbers)
result_2 = (x.is_integer() for x in numbers)
result_3 = map(float.is_integer, numbers)
# [True, False, True, False]

filter(lambda x: x>10, numbers)


numericList = [12, 24, 36, 48, 60, 72, 84]
print(sorted(numericList, reverse=True))#84,72,60....

a,*b=[1,2,3]#1 [2, 3]

Iterable and Iterator

from typing import *
print(isinstance(iter(m), Iterator))#True
print(isinstance(m, Iterator))#False
print(isinstance(m, Iterable))#True
print(isinstance(iter(m), Iterable))#True

Python Basics

built-in types

Numericint, float, complex
Stringstr (text sequence type)
Sequencelist, tuple, range
Binarybytes, bytearray, memoryview
Setset, frozenset
print(True==1)  # True
print(False==0)  # True
print(True==2)  # False
print(False==2)  # False
#numeric int,float,complex
#sequence list,tuple,range,bytes,str

#set and dect

Numerical Operations

from decimal import Decimal
from fractions import Fraction
#math for real numbers, cmath for complex numbers
import math, cmath

print(f.numerator, f.denominator)#607985949695017 1125899906842624
print(f.numerator, f.denominator)#27 50
print(f.numerator, f.denominator)#22 7
# converting to float
# converting to int

#complex numbers
a=67j#complex number complex(0,67)
##converting to polar form


#floor and ceil


(19/155)*(155/19) #0.9999999999999999
round((19/155)*(155/19)) #1
(19/155)*(155/19) == 1.0#False
math.isclose((19/155)*(155/19), 1)#True

value= 0x110 #0b10001000

import random
rand_val = random.randint(1, 100)
print(random.randrange(0, 100, 5))# 0, 5, 10...95
print(random.randint(1, 100))# 1, 2, 3...100


In Python, single-quoted strings and double-quoted strings are the same. This PEP does not make a recommendation for this. Pick a rule and stick to it. When a string contains single or double quote characters, however, use the other one to avoid backslashes in the string

#single line
m:str="hello world" 
#multi lines
m2:str="""hello world
my name is python""" 

print(m_digit.isnumeric())# True
print(m_digit.isalpha())# False

regular expressions

import re

string:str = 'hello 12 hi 89. Howdy 34'
regex:re= re.compile(r'(\d+)')
result = regex.findall(string)#['12', '89', '34']

regex2:re= re.compile(r'(\d+) hi')
print(,result.start(0),result.end(0))#12 hi 6 11
print(,result.start(1),result.end(1))#12 6 8

string:str = 'hello 12 hi 89. Howdy 34'
result = re.sub(r'\d+', '', string)# remove all digits

formatted strings

f'{id:s}  : {location:s} : {max_temp:d} / {min_temp:d} / {precipitation:f}'
f'{id:3d}  : {location:19s} : {max_temp:3d} / {min_temp:3d} / {precipitation:5.2f}' #'IAD  : Dulles Intl Airport :   32 /  13 /  0.40'

value= 42
string:str = f'{value} {2**7+1}'# 42 129
string:str = f'{value=} {2**7+1=}'# value=42 2**7+1=129

string:str = f'{value:b} {0.2:2.0%}'# 10001000 20%

m="hello %s %s %s" % ("world", "!" , 1)

string module

import string
value="Hello, World!"

single byte sequence b’hello world’

value="محمد العبدلي"
print(value.encode('utf-8', 'ignore'))#b'\xd9\x85\xd8\xad\xd9\x85\xd8\xaf \xd8\xa7\xd9\x84\xd8\xb9\xd8\xa8\xd8\xaf\xd9\x84\xd9\x8a'
print(len(value.encode('utf-8', 'ignore')))#23


m:tuple =(1,23,4,5,6,7,5,9,10)

v1,v2,v3,v4,v5,v6,v7,v8,v9 = m
print(v1,v2,v3,v4,v5,v6,v7,v8,v9)#1 23 4 5 6 7 5 9 10

Handling Exceptions

def sum_n(n: int) -> int:
    if(n < 0):
        raise Exception("n must be a positive integer")
    s = 0
    for i in range(1, n+1):
        s += i
    return s

except Exception as e:
         target = shutil.copy(source_path, target_path)
     except FileNotFoundError:
             target_path.parent.mkdir(exist_ok=True, parents=True)
             target = shutil.copy(source_path, target_path)
         except OSError as ex2:
             print(f"{target_path.parent} problem: {ex2}")
    except OSError as ex:
         print(f"Copy {source_path} to {target_path} error {ex}")


def hex2rgb(hx_int):
    if isinstance(hx_int, str):
        if hx_int [0] == "#":
            hx_int = int(hx_int [1:], 16)
            hx_int = int(hx_int, 16)
    r, g, b = (hx_int >> 16) & 0xff, (hx_int >> 8) & 0xff, hx_int & 0xff
    return r, g, b


def hex2rgb(hx_int: Union[int, str]) -> Tuple[int, int, int]:
    if isinstance(hx_int, str):
        if hx_int[0] == "#":
            hx_int = int(hx_int[1:], 16)
            hx_int = int(hx_int, 16)
    r, g, b = (hx_int >> 16)&0xff, (hx_int >> 8)&0xff, hx_int&0xff
    return r, g, b

RGB = Tuple[int, int, int]
HSL = Tuple[float, float, float]
def rgb_to_hsl(color: RGB) -> HSL:
def hsl_complement(color: HSL) -> HSL:
def hsl_to_rgb(color: HSL) -> RGB:
from typing import Union
from decimal import Decimal
def add(a:number,b:number)->number:
    return a+b
def dice_t(n: int, sides: int = 6) -> Tuple[int, ...]:
    return tuple(random.randint(1, sides) for _ in range(n))
  • * is used as a prefix for a special parameter that receives all the unmatched positional arguments. We often use *args to collect all of the positional arguments into a single parameter named args.
  • ** is used a prefix for a special parameter that receives all the unmatched named arguments. We often use **kwargs to collect the named values into a parameter named kwargs.
  • *, when used by itself as a separator between parameters, separates those parameters. It can be applied positionally or by keyword. The remaining parameters can only be provided by keyword.
def mm(*args):
mm(1,2,3,4)#(1, 2, 3, 4)

def mm(**args):
mm(a=1,b=2,c=3)# {'a': 1, 'b': 2, 'c': 3}

def mm(*, x, y):
    return x, y
print(mm(x=1, y=2))  # (1, 2)

iterable function

def fibo_iter() -> typing.Iterable[typing.Tuple[int, int]]:
    a = 1
    b = 1
    while True:
        yield (a, b)
        a, b = b, a + b

for i, f in fibo_iter():
    if i >= 10:
    print(f, end=' ')


def factorial(n: int) -> int:
        return factorial(n-1)*n
        return 1
print(factorial(5))  # 120


lamb= lambda x: x**2

typing module

v:typing.Union[int,float] = 1.0#union for merge many types in one type
print(isinstance(v,typing.Union[float,set,dict])) #true

#object is super of all classes
m = 12

callable:typing.Callable= lambda m: m+12
print(callable(12),isinstance(callable,typing.Callable))#24 true

Vector: TypeAlias = list[float]

UserId = NewType('UserId', int)
some_id = UserId(524313)


def decorator(func):
    def wrapper(*args, **kwargs):
        print(args, kwargs)
        print('Before function')
        func(*args, **kwargs)
        print('After function')
    return wrapper

def say_hello(name):
    print(f'Hello {name}')

C# Index and Range


int[] numbers = Enumerable.Range(0, 100).ToArray();
int x = 12;
int y = 25;
int z = 36;

Console.WriteLine($"{numbers[^x]} is the same as {numbers[numbers.Length - x]}");
Console.WriteLine($"{numbers[x..y].Length} is the same as {y - x}");

Console.WriteLine("numbers[x..y] and numbers[y..z] are consecutive and disjoint:");
Span<int> x_y = numbers[x..y];
Span<int> y_z = numbers[y..z];
Console.WriteLine($"\tnumbers[x..y] is {x_y[0]} through {x_y[^1]}, numbers[y..z] is {y_z[0]} through {y_z[^1]}");

Console.WriteLine("numbers[x..^x] removes x elements at each end:");
Span<int> x_x = numbers[x..^x];
Console.WriteLine($"\tnumbers[x..^x] starts with {x_x[0]} and ends with {x_x[^1]}");

Console.WriteLine("numbers[..x] means numbers[0..x] and numbers[x..] means numbers[x..^0]");
Span<int> start_x = numbers[..x];
Span<int> zero_x = numbers[0..x];
Console.WriteLine($"\t{start_x[0]}..{start_x[^1]} is the same as {zero_x[0]}..{zero_x[^1]}");
Span<int> z_end = numbers[z..];
Span<int> z_zero = numbers[z..^0];
Console.WriteLine($"\t{z_end[0]}..{z_end[^1]} is the same as {z_zero[0]}..{z_zero[^1]}");


var words = "my name is mohammed alabdali i am from jeddah i study master".Split(" ");
string[] allWords = words[..]; // all texts
string[] firstPhrase = words[..4]; //my name is mohammed
string[] lastPhrase = words[9..]; //i study master
Range implicitRange = 3..^5;

Range explicitRange = new(
    start: new Index(value: 3, fromEnd: false),
    end: new Index(value: 5, fromEnd: true));

if (implicitRange.Equals(explicitRange))
        $"The implicit range '{implicitRange}' equals the explicit range '{explicitRange}'");
// Sample output:
//     The implicit range '3..^5' equals the explicit range '3..^5'

Entity Framework With Mysql

packages you have to install

         <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="7.0.5" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="7.0.5" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5">
          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />

Create Tables Classes

assume we have two tables like this

Students table
Courses table

then we will build class or record for it like

public class Student
    public string StudentId { get; set; }
    public string? StudentName { get; set; }
    public string? cid { get; set; }
    public Course? Course { get; set; }

public class Course
    public string CourseId { get; set; }
    public string? CourseName { get; set; }
    //public ICollection<Student> Students { get; set; }

build the context

public class SchoolContext : DbContext
    public DbSet<Student> Students { get; set; }
    public DbSet<Course> Courses { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
              // configure connection
        if (!optionsBuilder.IsConfigured)
    // build relations 
    protected sealed override void OnModelCreating(ModelBuilder modelBuilder)

        modelBuilder.Entity<Student>().HasOne<Course>(s => s.Course).WithMany().HasForeignKey("cid");
        //modelBuilder.Entity<Course>().HasMany<Student>(s => s.Students).WithOne(s=>s.Course).HasForeignKey((e)=>e.CourseId);

then you can use it directly

using(SchoolContext context = new SchoolContext())
    var res2= context.Students.Include(s=>s.Course).ToList();
    foreach (var item in res2)

or use it as a service

builder.Services.AddDbContext<SchoolContext>(c =>
    c.UseMySql("connection string",  MySqlServerVersion.AutoDetect("connection string"));