대퍼에서 추상 모델 쿼리하기

c# dapper inheritance table-per-hierarchy

문제

나는 파생 된 모든 유형에 대한 열이 단일 테이블에있는 계층 당 데이터베이스 상속 테이블을 사용하고 있습니다. 파생 된 각 테이블은 파생 클래스의 이름을 포함하는 문자열 Discriminator 필드를 사용하여 식별됩니다.

---------------------
| tanimal           |
---------------------
| animalid          |
| discriminator     |
| furcolour         |
| feathercolour     |
---------------------

public abstract class Animal
{
    public int AnimalId { get; set; }
    public string Discriminator { get { return GetType().Name; } }
}

public class Bird : Animal
{
    public string FeatherColour { get; set; }
}

public class Dog : Animal
{
    public string FurColour { get; set; }
}

예상대로, Dapper의 쿼리 메소드를 통해이를 검색 할 때 Instances of abstract classes cannot be created . 나는 이것이 각각의 파생 된 타입 인 값을 갖는 Animal의리스트를 반환하기를 희망한다.

var animals = Connection.Query<Animal>("SELECT * FROM tanimal")

이에 대한 지원을 추가하려는 나의 시도는 성공적이지 못했습니다. 전달되는 형식이 추상 클래스 인 경우 SqlMapper.cs :: GetTypeDeserializer ()가 호출되기 전에 형식을 다음 메서드에서 반환 된 형식으로 바꿉니다.

static Type GetDerivedType(Type abstractType, IDataReader reader)
{
    var discriminator = abstractType.GetProperty("Discriminator");
    if (discriminator == null)
        throw new InvalidOperationException("Cannot create instance of abstract class " + abstractType.FullName + ". To allow dapper to map to a derived type, add a Discriminator field that stores the name of the derived type");

    return Type.GetType((string)reader["Discriminator"]);
}

그러나이 시점에서 리더가 열리지 않았 Invalid attempt to read when no data is present 실패합니다.

이것이 올바른 접근 방식입니까? 다른 곳에서 이것을 지원하려는 노력이 있었습니까?

인기 답변

이 작업을 수행 할 수는 있지만 별도의 테이블에서 Dapper의 기본 동작을 사용하는 것보다 효율적이지 않습니다.

GetDeserializer 는 모든 행에 대해 호출 GetDeserializer 합니다. 즉, 내부에서 일어날 필요가 있음을 의미합니다 while (reader.Read())

QueryImpl<T> 을 수정하면 원하는 결과를 얻을 수 있습니다. 다음과 같이 결과를 얻고 있다고 가정합니다.

var results = connection.Query<Animal>("SELECT * FROM tanimal");

그러면 QueryImpl<T>try {} 블록 시작 부분은 다음과 같습니다.

try
{
cmd = command.SetupCommand(cnn, info.ParamReader);

if (wasClosed) cnn.Open();

// We can't use SequentialAccess any more - this will have a performance hit.
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
wasClosed = false; 

// You'll need to make sure your typePrefix is correct to your type's namespace
var assembly = Assembly.GetExecutingAssembly();
var typePrefix = assembly.GetName().Name + ".";

while (reader.Read())
{
    // This was already here
    if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57
        yield break;

    // This has been moved from outside the while
    int hash = GetColumnHash(reader);

    // Now we're creating a new DeserializerState for every row we read 
    // This can be made more efficient by caching and re-using for matching types
    var discriminator = reader["discriminator"].ToString();
    var convertToType = assembly.GetType(typePrefix + discriminator);

    var tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(convertToType, reader, 0, -1, false));
    if (command.AddToCache) SetQueryCache(identity, info);

    // The rest is the same as before except using our type in ChangeType
    var func = tuple.Func;

    object val = func(reader);
    if (val == null || val is T)
    {
        yield return (T)val;
    }
    else
    {
        yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
    }
}
// The rest of this method is the same

이렇게하면 discriminator 필드에서만 메서드가 작동하므로 다른 쿼리와 정상적으로 작동하는 데 필요한 경우 사용자 고유의 QueryImpl<T> 을 만들 수 있습니다. 또한 모든 경우에 작동 할 것이라고 보장 할 수는 없으며 각 행 유형 중 하나에서 두 행만 테스트합니다. 그러나 이것이 좋은 출발점이되어야합니다.



아래 라이선스: CC-BY-SA with attribution
와 제휴하지 않음 Stack Overflow
아래 라이선스: CC-BY-SA with attribution
와 제휴하지 않음 Stack Overflow