개체 관계 맵(예: 솔리드 프레임)은 어떻게 작동합니까?

25938 단어 csharpeform
응용 프로그램이나 사이트를 데이터베이스에 연결할 때, 대부분의 경우 데이터베이스를 조회하고 결과를 강한 유형의 대상에 비추는 것이다.ORM과 유사한 Enitity 프레임워크 또는 Micro ORM과 유사한 Dapper를 계속 사용하는 경우 객체의 매핑과 C# 및 SQL 간 질의의 변환을 처리합니다.
본문은 3rd annual C# advent calendar의 일부분이다.나는 최초로 my blog에 발표했다.
ORMs의 사상은 매우 간단하다. 대부분의 코드 매핑 코드는 기계적이고 중복되기 때문에 자동화에 매우 적합하다.다음 모드가 포함된 Person 테이블이 있다고 가정합니다.
종대
데이터 형식
신분증
지력.
성함
nvarchar(50)
높이.
떠다니다
생일 축하합니다
약속 시간
우리는 상응하는 Person 종류가 하나 있다.
class Person
{
   public int Id { get; set; }
   public string Name { get; set; }
   public double Height { get; set; }
   public DateTime Birthdate { get; set; }
}
데이터를 수동으로 검색하여 클래스 목록Person에 매핑하는 경우 C# 코드는 다음과 같습니다.
public async Task<List<Person>> GetPeople()
{
    var list = new List<Person>();

    var sql = "SELECT Id, Name, Height, Birthdate From Person";

    using (var connection = new SqlConnection(ConnectionString))
    using (var command = new SqlCommand(sql, connection))
    {
        await connection.OpenAsync();

        using (var reader = await command.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                var person = new Person
                {
                    Id = (int)reader["Id"],
                    Name = (string)reader["Name"],
                    Height = (double)reader["Height"],
                    Birthdate = (DateTime)reader["Birthdate"],
                };

                list.Add(person);
            }
        }
    }

    return list;
}
SQL 쿼리와 속성에 대한 매핑은 테이블/클래스 간에 유일하게 다른 두 가지입니다.만약 우리가 클래스와 그 유형의 속성 목록을 얻을 수 있다면...사실은 우리가 할 수 있다는 것을 증명한다!
시스템반성하다.유형은 하나를 대표한다.네트워크 유형.그것은 유형에 관한 많은 메타데이터가 있다.우리는 GetProperties 방법을 사용하여 유형 속성의 목록을 얻을 수 있다.
PropertyInfo[] properties = typeof(Person).GetProperties();
typeof 키워드는 컴파일할 때 클래스를 가져오는 데 사용됩니다.PropertyInfo클래스에는 많은 유용한 속성과 방법이 있지만 우리는 이것에만 관심이 있습니다.
구성원
타입
묘사
성함
꿰미
속성의 이름을 가져옵니다.
GetMethod
MethodInfo
속성을 얻는 get 방법.
설정 방법
MethodInfo
속성을 얻는 set 방법.
예를 들어, 다음 코드가 있는 경우
var person = new Person
{
    Name = "Ahmed",
    Height = 180.4,
    Birthdate = new DateTime(1998, 1, 1),
    Id = 24
};

var heightProperty = typeof(Person).GetProperty(nameof(Person.Height));
heightProperty.SetValue(person, 185);

var properies = typeof(Person).GetProperties();
foreach (var prop in properies)
{
    Console.WriteLine($"{prop.Name} => {prop.GetValue(person)}");
}
출력:
Height => 185
Id => 24
Name => Ahmed
Birthdate => 1/1/1998 12:00:00 AM
주의: 흥미로운 것은 우리가 Height를 사용하여 순환 상단GetProperty의 메타데이터를 얻었기 때문에 GetProperties에서 속성을 되돌리는 순서가 바뀌었고 Height가 첫 번째 속성이다.
보시다시피 반사를 사용하면 높이의 값이 180.4에서 185로 변경됩니다.
두 번째는 SQL 쿼리를 생성하는 것입니다. 이 또한 매우 간단합니다.
var columnNames = properties.Select(p => p.Name);
var columns = string.Join(", ", columnNames);

var sql = $"SELECT {columns} FROM {typeof(T).Name}";
Console.WriteLine(sql);
주의: 왜 GetSelectQuery 마지막에 <T>가 있는지 확실하지 않다면, 그것은 이 방법이 일반적이기 때문이다.그것은 프로그래머가 좋아하는 모든 종류의 호출을 허용한다. GetSelectQuery<T>위의 예에서 우리는 그것을 Person와 함께 사용한다.범형은 매우 강한 개념이다.당신은 그 중에서 범주형에 관한 더 많은 정보를 알 수 있습니다.
출력:
SELECT Id, Name, Height, Birthdate FROM Person
생성된 SQL 쿼리를 자세히 살펴보면 직접 작성한 쿼리와 동일합니다.따라서 이제 우리는 새로운 지식을 결합하여 데이터베이스에서 우리가 좋아하는 모든 표를 조회하고 결과를 c# 유형에 비추는 방법을 작성할 수 있다.
public async Task<List<T>> GetAll<T>() where T : new()
{
    var list = new List<T>();

    var properties = typeof(T).GetProperties();

    var columnNames = properties.Select(p => p.Name);
    var columns = string.Join(", ", columnNames);

    var sql = $"SELECT {columns} FROM {typeof(T).Name}";

    using (var connection = new SqlConnection(ConnectionString))
    using (var command = new SqlCommand(sql, connection))
    {
        await connection.OpenAsync();

        using (var reader = await command.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                var model = new T();

                foreach (var property in properties)
                {
                    property.SetValue(model, reader[property.Name]);
                }

                list.Add(model);
            }
        }
    }

    return list;
}
주의: new() 제약은 T에 파라미터가 없는 공공 구조 함수를 확보합니다.이렇게 하면 우리는 쓸 수 있다var model = new T();.
다음과 같이 사용할 수 있습니다.
List<Person> people = GetAll<Person>();
깔끔하죠?
다음은 InsertUpdate의 모습입니다.
public async Task Insert<T>(T item)
{
    var properties = typeof(T).GetProperties();

    var columns = string.Join(", ", properties.Select(p => p.Name));
    var columnParameters = string.Join(", ", properties.Select(p => $"@{p.Name}"));

    var sql = $"INSERT INTO {typeof(T).Name} ({columns}) VALUES ({columnParameters})";

    using (var connection = new SqlConnection(ConnectionString))
    using (var command = new SqlCommand(sql, connection))
    {
        await connection.OpenAsync();

        foreach (var property in properties)
        {
            command.Parameters.AddWithValue($"@{property.Name}", property.GetValue(item));
        }

        await command.ExecuteNonQueryAsync();
    }
}

public async Task Update<T>(string idPropertyName, T item)
{
    var properties = typeof(T).GetProperties();

    var columnUpdates = properties.Where(p => p.Name != idPropertyName)
                                  .Select(p => $"{p.Name} = @{p.Name}");
    var columns = string.Join(", ", columnUpdates);

    var sql = $"UPDATE {typeof(T).Name} SET {columns} WHERE {idPropertyName} = @{idPropertyName}";

    using (var connection = new SqlConnection(ConnectionString))
    using (var command = new SqlCommand(sql, connection))
    {
        await connection.OpenAsync();

        foreach (var property in properties)
        {
            command.Parameters.AddWithValue($"@{property.Name}", property.GetValue(item));
        }

        await command.ExecuteNonQueryAsync();
    }
}
보시다시피, 몇 줄의 코드만 있으면, 우리는 가장 작은 마이크로 ORM을 실현할 수 있습니다.하지만 ORM에는 여러 가지 특성이 있다.그 중 하나는 ORMs는 보통 여러 개의dbms를 지원하는데, 우리는 이 실현에서 MSSQL만 지원한다. (비록 이런 간단한 맵에 대해서는 다른dbms를 지원하는 것이 어렵지 않지만.)또 다른 일은 데이터베이스를 조회하는 방법 (GetAll 에where 자구를 추가하는 것) 을 제공하는 것이다.그것은 통상적으로 사용 ExpressionTree s으로 이루어진다.
우리가 실시하는 방식에 따르면 그것은 좋은 성능을 가지지 못할 것이다.성능을 향상시키는 간단한 방법은 반사 비용이 매우 비싸기 때문에 캐시 종류별로 PropertyInfo 사용하고 다시 사용하는 것이다.실제 ORM은 c#단과 그것들이 SQL을 생성하는 방식에 있어 더욱 최적화되었다.
본문의 코드on GitHub는 사용할 수 있습니다.
면책 성명: 본고의 예시 코드는 시범적인 목적에만 사용되며 생산 환경에서 사용해서는 안 된다.

좋은 웹페이지 즐겨찾기