当前位置: 动力学知识库 > 问答 > 编程问答 >

c# - How to convert List<Dapper.SqlMapper.FastExpando> to runtime type (List<IDictionary<string,object>>)

问题描述:

I'm using a method that has a return signatur of IEnumerable<dynamic>. At runtime for a particular call it is returning List<Dapper.SqlMapper.FastExpando>.

var x0 = repo.Find(proc, param);

//x0 runtime type is {List<Dapper.SqlMapper.FastExpando>}

LINQPad.Extensions.Dump indicates the runtime type of x0 is: List<IDictionary<String, Object>> but I can't seem to cast/convert to List<IDictionary<String, Object>>.

Here is a screenshot of the Linqpad Dump:

Ultimately I need to join all the Values from each Dictionary to a single IEnumerable<DateTime>.

IEnumerable<DateTime> GetDates(int productId)

{

const string proc = "[dbo].[myproc]";

dynamic param = new { Id = "asdf" };

var x0 = repo.Find(proc, param);

//...

//linq conversion from x0 to IEnumerable<DateTime> here.

}

Error I'm getting

List<IDictionary<String, Object>> x5 = repo.Find(proc, param);

results in:

RuntimeBinderException: Cannot implicitly convert type 'object' to

'System.Collections.Generic

.List<System.Collections.Generic.IDictionary<string,object>>'.

An explicit conversion exists (are you missing a cast?)

BACKGROUND:

I am using a Dapper wrapper and can't change the database table/stored procedure which returns denormalized results. Instead of 100 rows of 1 data element, the sproc returns 1 row of 100 columns. I wanted to avoid creating a class to represent the 100 columns and want to take advantage of Dapper's automagical ability to transpose the column data to rows via an IDictionary of columnName, columnValue.

UPDATE

This appears to be an issue with the dynamic param. When specified inline, it works. If specified locally and then passes as a parameter, it fails.

IEnumerable<DateTime> GetDates(int productId)

{

const string proc = "[dbo].[myproc]";

dynamic param = new { Id = "asdf" };

//next line throws RuntimeBinderException: 'object' does not

//contain a definition for 'First'.

//IDictionary<String, object> x0 = repo.Find(proc, param).First();

//this succeeds:

IDictionary<String, object> x0 = repo.Find(proc, new { Id = "asdf" }).First();

IEnumerable<DateTime> qry2

= x0.Values.AsQueryable()

.Where(x => x != null)

.Select(x => (DateTime) x);

return qry2;

}

Here are the signatures for Find and Query:

//Repository::Find

public IEnumerable<dynamic> Find(string procName, object param = null)

//Dapper SqlMapper::Query

public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)

网友答案:

You can use the LINQ Cast method to cast each item in the sequence.

List<IDictionary<String, Object>> data = repo.Find(proc, param)
    .AsEnumerable()
    .Cast<IDictionary<String, Object>>()
    .ToList();
网友答案:

Repo.Find does not return an IEnumerable<dynamic>. It returns object. If you are sure it will always return a List<IDictionary<String, Object>> just do this:

List<IDictionary<String, Object>> x5 = (List<IDictionary<String, Object>>)repo.Find(proc, param);
网友答案:

So the problem is that by specifying param as dynamic causes the method to be dynamically invoked and extension methods can no longer be chained.

So you could just use var instead of dynamic:

 var param = new { Id = "asdf" };
 var data = repo.Find(proc, param)
.Cast<IDictionary<String, Object>>()
.ToList();

Or just cast the dynamic param to object before invoking:

 var data = repo.Find(proc, (object)param)
.Cast<IDictionary<String, Object>>()
.ToList();

Or if Find really is returning a runtime type of System.Collections.Generic.List<Dapper.SqlMapper.FastExpando> just cast the result to dynamic to make sure that it uses it's runtime type before assigning it, and it is important to assign to IEnumerable<> specifically because it is a co-variant type (IList<> or List<> will fail).

IEnumerable<IDictionary<String, Object>> data = (dynamic) repo.Find(proc, param);
网友答案:

Solution

Don't pass back dynamic from your method. Dapper has a ToList< T > extension method, so you can cast to any supported type. In this case if you really need the low level associative array, pass back List< Dictionary < string, object > >.

It's also worth noting C# dynamic does not marshal across assembly boundaries, so that may have potentially been an issue you encountered. If you're marshaling across assemblies, you'd want to use Expando, List< Dictionary < string, object > >, or some other type that is referenced by both assemblies.

Explanation

Here's an older version of the Dapper SQLMapper source that includes FastExpando (line 941). Notice the class definition for FastExpando.

private class FastExpando : System.Dynamic.DynamicObject, IDictionary< string, object >

分享给朋友:
您可能感兴趣的文章:
随机阅读: