.NET용 Norm Micro ORM이 Raw DataReader만큼 빠른 이유

8090 단어
Norm 최근에 매우 진지해진 사이드 프로젝트 중 내가 가장 좋아하는 프로젝트 중 하나입니다.

기본적으로 몇 가지 추가 트릭이 있는 .NET용 또 다른 마이크로 ORM 라이브러리(Dapper 클론)입니다.

원래 예를 들어 튜플 및 비동기 스트리밍과 같은 최신 C# 기능을 활용할 수 있도록 작성했습니다.

// Map single values from a tuple to variables:
var (id, foo, bar) = connection.Single<int, string, string>("select id, foo, bar from my_table limit 1");

// Map to enumerable of named tuples:
IEnumerable<(int id, string foo, string bar)> results = connection.Read<int, string, string>("select id, foor, bar from my_table");

// Asynchronously stream values directly from database
await foreach(var (id, foo, bar) in connection.ReadAsync<int, string, string>("select id, foor, bar from my_table"))
{
    //...
}

// etc...


잘 작동하지만 실제 Micro ORM이 되려면 쿼리 결과를 클래스에 매핑하는 적절한 개체 매퍼가 필요합니다.

그러나 첫 번째 버전은 상당히 괜찮았지만 "King of Micro ORM"으로 표시된 Dapper의 유명한 매퍼만큼 성능이 좋지 않았습니다.

최근에 시도해보고 싶은 통찰력과 아이디어가 있어서 Norm 매퍼를 처음부터 다시 작성했습니다.

결과는 나조차도 놀랐습니다. 기본적으로 원시DataReader와 구별할 수 없을 정도로 빠른 성능을 기대하지 않았습니다.

어떻게 그게 가능합니까?

Norm에 대해 자세히 알아보겠습니다.

데이터 읽기



Norm은 매핑을 준비하기 위해 데이터 읽기에 사용되는 하나의 기본 확장 데이터를 구현합니다.

public static IEnumerable<(string name, object value)[]> Read(this DbConnection connection, string command) 


보시다시피, 이름과 값 튜플을 포함하는 배열을 생성하는 열거자를 반환합니다.

어떤 종류의 목록도 생성하지 않고 단순히 yield를 사용하여 열거가 트리거될 때 값을 반환합니다.

그리고 그 열거자 항목은 이름 및 값 튜플 형식의 필드 배열입니다. 여기서 이름은 필드 이름이고 값은 개체 유형의 실제 필드 값입니다(나중에 캐스팅해야 함).

실제 매핑 자체는 동일한 구조의 확장으로 구현됩니다.

public static IEnumerable<T> Map<T>(this IEnumerable<(string name, object value)[]> tuples)


마찬가지로 이 매핑 확장은 일종의 목록을 만드는 대신 열거에서 매핑된 결과를 생성합니다.

그렇기 때문에 Norm으로 작업할 때 Linq 확장ToList을 사용하여 실제 목록을 생성해야 하므로 열거 및 매핑도 트리거해야 합니다.

// build the enumerator, does not start reading 
// you can use also use connection.Query<MyClass>(query);
var enumaration = connection.Read().Map<MyClass>(query);
// start actual reading from the database and creates a list
var results = enumaration.ToList();


직렬화 또는 매핑 전에 이제 Linq 표현식을 작성할 수 있기 때문에 이것은 깔끔합니다.

이제 실제 인스턴스에 매핑(string name, object value)[]만 하면 됩니다.

데이터 매핑



간단히 말해서 매핑은 이 배열(string name, object value)[]의 값을 클래스 인스턴스로 복사하는 것입니다.

예를 들어 간단한 클래스가 있다고 가정해 보겠습니다.

public class MyClass 
{
    public int Id { get; init; }
    public string Foo { get; init; }
    public string Bar { get; init; }
}


그리고 당연히 데이터베이스 쿼리는 id , foo , bar 를 반환합니다.

이러한 값을 매핑하려면 각 반복 단계의 이름을 비교하여 idId , fooFoo , barBar 로 매핑해야 합니다.

그러나 파일id이 항상 첫 번째 값이고 foo가 항상 두 번째 값이라는 것을 이미 알고 있다면 어떻게 될까요? 각 반복에서 이름 문자열을 비교할 필요가 없기 때문에 훨씬 더 효율적입니다.

그것은 위치에 의한 매핑입니다. 이것은 일반적으로 사용되는 이름으로 매핑하는 것보다 훨씬 더 효율적입니다.

그러나 이름을 변경하지 않고 쿼리(또는 해당 문제에 대한 클래스)에서 순서를 바꾸면 오류와 혼란이 발생하기 때문에 이는 차선책입니다.

예를 들어 쿼리select foo, id, bar를 이 예의 클래스에 매핑하려고 하면 프로그램이 중단됩니다.

그러나 이름으로 매핑을 수행할 수 있다면(첫 번째 레코드에서만 위치 인덱스를 기억하고) 나중에 동일한 유형의 다른 모든 반복에서 사용할 수 있도록 이름으로 매핑을 수행할 수 있다면 각 유형에 대해 한 번만 이름으로 매핑을 수행할 것입니다. . 그것은 빠를 것입니다.

이것이 바로 Norm 매핑이 작동하는 방식입니다.

이것은 또한 나에게 여분의 호흡 공간을 제공하므로 이제 예를 들어 낙타 대소문자 명명 또는 일반 대소문자 명명을 사용하는 것과 같은 더 복잡한 매핑을 수행할 수 있습니다.

개체 구성

좋은 웹페이지 즐겨찾기