我正在使用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的查询方法检索它时,我无法创建抽象类的实例.我希望这会返回一个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"]); }
但是,此时看起来还没有打开读取器,因此当没有数据存在时无效尝试读取失败.
解决方法
你可以使这个工作,但它不如使用Dapper的默认行为与单独的表.
需要为每一行调用GetDeserializer,这意味着它需要在内部发生(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,-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
这将使该方法仅适用于鉴别器字段,因此您可能想要创建自己的QueryImpl< T>如果你需要这个与其他查询正常工作.此外,我不能保证这将在每种情况下都有效,只测试两行,每种类型一个 – 但这应该是一个很好的起点.