개체 관계 맵(예: 솔리드 프레임)은 어떻게 작동합니까?
본문은 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>();
깔끔하죠?다음은
Insert
와 Update
의 모습입니다.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는 사용할 수 있습니다.
면책 성명: 본고의 예시 코드는 시범적인 목적에만 사용되며 생산 환경에서 사용해서는 안 된다.
Reference
이 문제에 관하여(개체 관계 맵(예: 솔리드 프레임)은 어떻게 작동합니까?), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/mhmd_azeez/how-do-object-relational-mappers-like-entity-framework-work-2ko5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)