有时候我们需要在EF里直接执行参数化的SQL语句,如果有返回,就要把结果映射成C#对象集合。貌似是从EF4.2开始(4.0写法不一样)提供了DbContext.Database.SqlQuery<T>()的API可以直接执行SQL。但是如果碰到可控类型的参数,比如Guid?,就会爆。
看一个例子,定义的SQL语句如下:
string sql = @"SELECT p2.Id AS PeriodId,
p2.Title AS PeriodTitle,
u.UserId AS UserId,
u.DisplayName AS UserDisplayName,
p.Id AS ProductId,
p.Title AS ProductTitle,
SUM(upa.Amount) AS TotalCount,
SUM(p.Price * upa.Amount) AS TotalPrice,
upa.Note AS Note
FROM Product AS p
INNER JOIN UserProductAssociation AS upa
ON upa.ProductId = p.Id
INNER JOIN Orders AS o
ON o.Id = upa.OrderId
INNER JOIN Period AS p2
ON p2.Id = o.PeriodId
INNER JOIN Users AS u
ON u.UserId = o.UserId
WHERE 1 = 1
AND (@userId IS NULL OR u.UserId = @userId)
AND (@periodId IS NULL OR p2.Id = @periodId)
GROUP BY
u.UserId,
u.DisplayName,
p2.Id,
p2.Title,
p.Id,
p.Title,
upa.Note";
其中@userId和@periodId均为Guid?类型的参数,比如传进来的时候是这样的:
public ActionResult GetStatGridData(..., Guid? userId, Guid? periodId)
...
如果你这样写:
_db.Database.SqlQuery<GetPersonStats_Result>(sql, new[]
{
userId,
periodId
}).ToList();
就会爆成这样:
No mapping exists from object type System.Nullable`1[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]][] to a known managed provider native type.
查了一些资料,说是要转DBNull进去,于是你这样写:
_db.Database.SqlQuery<GetPersonStats_Result>(sql, new[]
{
userId ?? (object)DBNull.Value,
periodId ?? (object)DBNull.Value
}).ToList();
结果还是爆:
Must declare the scalar variable "@userId".
最终发现,你得这样写:
_db.Database.SqlQuery<GetPersonStats_Result>(sql,
new SqlParameter("@userId", userId ?? (object)DBNull.Value),
new SqlParameter("@periodId", periodId ?? (object)DBNull.Value)).ToList();
就不会爆。
另外,这种写法不适用于SQL CE (已在EF5 + SQL CE上zuo die过)。不知道这是EF的bug还是我SB。总之写下此文以警后人。