Avatar ·

Соответствие типов данных C# и SQL Server: полное руководство

Всем привет! Я начинающий C# разработчик, пишу небольшое приложение для работы с базой данных SQL Server через Entity Framework. Столкнулся с проблемой: я не могу точно понять, какие типы данных в C# соответствуют типам в SQL Server.

Например, у меня есть таблица с колонками:

CREATE TABLE Users (
    Id INT PRIMARY KEY,
    Name NVARCHAR(100),
    Salary DECIMAL(18,2),
    IsActive BIT,
    CreatedAt DATETIME2
);

В C# я создаю модель:

public class User
{
    public int Id { get; set; }           // INT -> int, тут вроде понятно
    public string Name { get; set; }      // NVARCHAR -> string, тоже ясно
    public decimal Salary { get; set; }   // DECIMAL -> decimal?
    public bool IsActive { get; set; }    // BIT -> bool?
    public DateTime CreatedAt { get; set; } // DATETIME2 -> DateTime?
}

Но есть много других типов: BIGINT, SMALLINT, TINYINT, FLOAT, REAL, MONEY, UNIQUEIDENTIFIER, VARBINARY и т.д. Я постоянно путаюсь, особенно с числовыми типами. Например, какая разница между FLOAT и REAL в SQL и как их правильно маппить на C#? И что делать с типами, которых нет в C# напрямую, например, SQL_VARIANT или GEOGRAPHY?

Может кто-то составить полную таблицу соответствия? Буду очень благодарен за подробное объяснение с примерами!

<<>>

Полное соответствие типов данных SQL Server и C# (.NET)

При работе с SQL Server из C# (через ADO.NET, Entity Framework, Dapper или другие ORM) важно понимать точное соответствие типов. Вот исчерпывающая таблица и пояснения:

Основные типы данных (наиболее часто используемые)

SQL ServerC# (.NET)Примечания
INTint (Int32)4 байта, диапазон ±2.1 млрд
BIGINTlong (Int64)8 байт, очень большие числа
SMALLINTshort (Int16)2 байта, ±32,767
TINYINTbyte1 байт, 0-255 (беззнаковый!)
BITbooltrue/false или null
DECIMAL(p,s)decimalТочные десятичные числа
NUMERIC(p,s)decimalСиноним DECIMAL
MONEYdecimalСпециальный тип, но лучше маппить на decimal
SMALLMONEYdecimalАналогично
FLOAT(n)doubleПриблизительное число с плавающей точкой (8 байт)
REALfloatПриблизительное число (4 байта)
NVARCHAR(n)/NVARCHAR(MAX)stringЮникод-строка
VARCHAR(n)/VARCHAR(MAX)stringНеюникод, но в C# всё равно string
CHAR(n)/NCHAR(n)stringФиксированная длина
DATETIMEDateTimeТочность до 3.33 мс
DATETIME2(p)DateTimeБолее высокая точность (до 100 нс)
SMALLDATETIMEDateTimeТочность до 1 минуты
DATEDateTime (или DateOnly в .NET 6+)Только дата
TIME(p)TimeSpan (или TimeOnly в .NET 6+)Только время
DATETIMEOFFSET(p)DateTimeOffsetС часовым поясом
UNIQUEIDENTIFIERGuidGUID
VARBINARY(n)/VARBINARY(MAX)byte[]Массив байт
BINARY(n)byte[]Фиксированная длина
XMLstring (или XDocument/XElement)XML-данные
SQL_VARIANTobjectМожет содержать любой тип

Важные нюансы и примеры

1. Разница между FLOAT и REAL:

// SQL: FLOAT (53) -> C# double (8 байт)
double sqlFloat = 3.141592653589793;

// SQL: REAL (24) -> C# float (4 байта)
float sqlReal = 3.14159f;

// Внимание: точность разная!
double d = 0.1 + 0.2; // 0.30000000000000004
float f = 0.1f + 0.2f; // 0.3 (но это округление)

2. Работа с nullable типами:

// Если в SQL колонка допускает NULL, используйте nullable типы
public class Product
{
    public int Id { get; set; }           // NOT NULL
    public string? Description { get; set; } // NVARCHAR NULL
    public decimal? Price { get; set; }   // DECIMAL NULL
    public DateTime? DeletedAt { get; set; } // DATETIME2 NULL
}

3. Специальные типы (GEOGRAPHY, HIERARCHYID и др.):

// Для GEOGRAPHY используйте Microsoft.SqlServer.Types
// Установите NuGet пакет: Microsoft.SqlServer.Types
using Microsoft.SqlServer.Types;

public class Location
{
    public SqlGeography GeoPoint { get; set; } // Для хранения координат
}

// Или используйте сторонние библиотеки (NetTopologySuite для EF Core)

4. Рекомендации по выбору типов:

  • Для целых чисел: используйте наименьший подходящий тип (byte, short, int, long)
  • Для денег: всегда decimal, никогда не float/double (из-за погрешностей)
  • Для дат: DateTime для большинства случаев, DateTimeOffset если важен часовой пояс
  • Для больших строк: используйте NVARCHAR(MAX) с типом string (но будьте осторожны с производительностью)

5. Пример полной модели с правильными типами:

public class Employee
{
    public int Id { get; set; }                    // INT
    public string FullName { get; set; }           // NVARCHAR(200)
    public byte Age { get; set; }                  // TINYINT (0-255)
    public decimal Salary { get; set; }            // DECIMAL(18,2)
    public double Height { get; set; }             // FLOAT
    public bool IsActive { get; set; }             // BIT
    public DateTime BirthDate { get; set; }        // DATE
    public TimeSpan ShiftStart { get; set; }       // TIME
    public Guid Identifier { get; set; }           // UNIQUEIDENTIFIER
    public byte[] Avatar { get; set; }             // VARBINARY(MAX)
    public string? Notes { get; set; }             // NVARCHAR(MAX) NULL
}

Запомните главное правило: всегда используйте типы C#, которые точно соответствуют типам SQL по диапазону и точности. При сомнениях проверяйте документацию Microsoft или используйте профилировщик для отслеживания реальных преобразований.

<<>>

Как избежать ошибок при маппинге: практические советы

К таблице соответствия из первого ответа добавлю несколько важных моментов, которые помогут избежать типичных ошибок:

1. Используйте явное приведение типов при ручном маппинге

// Чтение из DataReader
using (var cmd = new SqlCommand("SELECT * FROM Users", connection))
using (var reader = cmd.ExecuteReader())
{
    while (reader.Read())
    {
        var user = new User
        {
            Id = reader.GetInt32(0),           // INT
            Name = reader.GetString(1),        // NVARCHAR
            Salary = reader.GetDecimal(2),     // DECIMAL
            IsActive = reader.GetBoolean(3),   // BIT
            CreatedAt = reader.GetDateTime(4)  // DATETIME2
        };
    }
}

2. Будьте осторожны с типами, которых нет в C#