有时候我们需要在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。总之写下此文以警后人。