在精巧的时候查询抽象模型

ajb*_*ven 8 c# inheritance table-per-hierarchy dapper

我正在使用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; }
}
Run Code Online (Sandbox Code Playgroud)

正如所料,当我通过Dapper的查询方法检索这个时,我会收到Instances of abstract classes cannot be created.我希望这会返回一个Animal列表,它们的值是相应的派生类型.

var animals = Connection.Query<Animal>("SELECT * FROM tanimal")
Run Code Online (Sandbox Code Playgroud)

我试图为此添加支持是不成功的.在传入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"]);
}
Run Code Online (Sandbox Code Playgroud)

然而,在这一点看来,读者还没有被打开,所以它失败了Invalid attempt to read when no data is present.

这是正确的方法吗?是否有任何努力在其他地方支持这一点?

emb*_*bee 5

您可以做到这一点,但它的效率会低于使用 Dapper 的默认行为和单独的表。

GetDeserializer需要为每一行调用,这意味着它需要发生在内部while (reader.Read())

通过修改QueryImpl<T>就可以达到你想要的结果。假设您得到的结果是:

var results = connection.Query<Animal>("SELECT * FROM tanimal");
Run Code Online (Sandbox Code Playgroud)

try {}那么块的开头QueryImpl<T>将是:

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
Run Code Online (Sandbox Code Playgroud)

这将使该方法仅适用于鉴别器字段,因此QueryImpl<T>如果您需要此方法与其他查询正常工作,您可能需要创建自己的方法。另外,我不能保证这在每种情况下都有效,仅使用两行进行测试,每种类型之一 - 但这应该是一个很好的起点。


小智 5

我也想分享我的解决方案。输入:

C#

abstract class Stock {}
class Bond: Stock {}
class Equity : Stock {}
Run Code Online (Sandbox Code Playgroud)

SQL

CREATE TABLE [dbo].[Stocks] (
....some columns....
    [Descriminator] VARCHAR (100) NOT NULL,
);
Run Code Online (Sandbox Code Playgroud)

在 SQL 中,我有一个描述符列,它确定每行“股票”或“债券”的 C# 类型。基本上,这是按层次结构表策略的标准实现。

我使用了 Dapper 的无参数查询语法

connection.Query(sql); 
Run Code Online (Sandbox Code Playgroud)

获取dynamicDapper 视为 DapperRow 的对象。尽管 DapperRow 是一个私有类,但它实现了IDictionary<string, object>.String - 属性名称、Object - 属性值。

函数将 IDictionary<string, object> 转换为类 (强类型):

public static T GetObject<T>(IDictionary<string, object> dict)
{
    Type type = typeof(T);
    var obj = Activator.CreateInstance(type);

    foreach (var kv in dict)
    {
        type.GetProperty(kv.Key).SetValue(obj, kv.Value);
    }
    return (T)obj;
}
Run Code Online (Sandbox Code Playgroud)

鉴别器列和 C# 类之间的映射器:

public static Stock ConvertToStock(object value)
{
    var dapperRowProperties = value as IDictionary<string, object>;
    switch (dapperRowProperties["Descriminator"])
    {
        case "Bond":
            return GetObject<Bond>(dapperRowProperties);
        case "Stock":
            return GetObject<Stock>(dapperRowProperties);
        default:
            return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

转换器的使用:

public Stock GetStock(int id)
{
    Stock stock;
    var sql = "select * from Stocks where Id = @id";
    using (var connection = ConnectionFactory.GetOpenConnection())
    {
        stock = connection.Query(sql, new { id }).Select(ConvertToStock).Single();
    }
    return stock;
}
Run Code Online (Sandbox Code Playgroud)