11-6.从一个”模型定义”函数里返回一个复杂类型
问题
想要从一个”模型定义”函数返回一个复杂类型
解决方案
假设我们有一个病人(patient)和他们访客(visit)的模型,如 Figure 11-6所示 .
Figure 11-6. A model for patient visits
我们想要创建一个”模型定义”函数,返回一个概要信息,包括:病人名字,病人的访客数,和病人累积的账单. 此外,我们只过滤出年龄超过40岁的病人:
1. 在模型设计视图上,右击, 新建 ➤ 复杂类型.
2.在模型浏览器里右击新建的复杂类型,重命名为VisitSummary, 然后给复杂属性添加下列属性::
a. Name: String,不可为null
b. TotalVisits: Int32, 不可为null
c. TotalCost:Decimal, 不可为null
3. 在解决方案资源管理器中右击.edmx 文件, 打开方式 ➤ XML 编辑器.
4. 在.edmx 文件的概念模型conceptual models)的<Schema> 标签下插入Listing 11-11所示的代码,这样函数主定义好了.
Listing 11-11. The GetVisitSummary() Model-Defined Function
<Function Name="GetVisitSummary" ReturnType="Collection(EFRecipesModel.VisitSummary)">
<DefiningExpression>
select VALUE EFRecipesModel.VisitSummary(pv.Patient.Name,
Count(pv.VisitId),Sum(pv.Cost))
from EFRecipesEntities.PatientVisits as pv
group by pv.Patient.Name
</DefiningExpression>
</Function>
5. 用如 Listing 11-12.所示的代码来插入和查询这个模型:
Listing 11-12. Using eSQL and LINQ with the VisitSummary() Function to Query the Model
class Program
{
static void Main(string[] args)
{
RunExample();
}
static void RunExample()
{
using (var context = new EFRecipesEntities())
{
string hospital = "Oakland General";
var p1 = new Patient { Name = "Robin Rosen", Age = 41 };
var p2 = new Patient { Name = "Alex Jones", Age = 39 };
var p3 = new Patient { Name = "Susan Kirby", Age = 54 };
var v1 = new PatientVisit
{
Cost = 98.38M,
Hospital = hospital,
Patient = p1
};
var v2 = new PatientVisit
{
Cost = 1122.98M,
Hospital = hospital,
Patient = p1
};
var v3 = new PatientVisit
{
Cost = 2292.72M,
Hospital = hospital,
Patient = p2
};
var v4 = new PatientVisit
{
Cost = 1145.73M,
Hospital = hospital,
Patient = p3
};
var v5 = new PatientVisit
{
Cost = 2891.07M,
Hospital = hospital,
Patient = p3
};
context.Patients.Add(p1);
context.Patients.Add(p2);
context.Patients.Add(p3);
context.SaveChanges();
}
using (var context = new EFRecipesEntities())
{
Console.WriteLine("Query using eSQL...");
var esql = @"Select value ps from EFRecipesEntities.Patients
as p join EFRecipesModel.GetVisitSummary()
as ps on p.Name = ps.Name where p.Age > 40";
var objectContext = (context as IObjectContextAdapter).ObjectContext;
var patients = objectContext.CreateQuery<VisitSummary>(esql);
foreach (var patient in patients)
{
Console.WriteLine("{0}, Visits: {1}, Total Bill: {2}",
patient.Name, patient.TotalVisits.ToString(),
patient.TotalCost.ToString("C"));
}
}
using (var context = new EFRecipesEntities())
{
Console.WriteLine();
Console.WriteLine("Query using LINQ...");
//译注:遇到了与11-5一样的异常
var patients = from p in context.Patients
join ps in context.GetVisitSummary() on p.Name equals
ps.Name
where p.Age >= 40
select ps;
foreach (var patient in patients)
{
Console.WriteLine("{0}, Visits: {1}, Total Bill: {2}",
patient.Name, patient.TotalVisits.ToString(),
patient.TotalCost.ToString("C"));
}
}
}
}
partial class EFRecipesEntities
{
[EdmFunction("EFRecipesModel", "GetVisitSummary")]
public IQueryable<VisitSummary> GetVisitSummary()
{
var objectContext = (this as IObjectContextAdapter).ObjectContext;
return objectContext.CreateQuery<VisitSummary>(
Expression.Call(Expression.Constant(this),
(MethodInfo)MethodInfo.GetCurrentMethod()).ToString());
}
}
Listing 11-12代码输出结果如下:
Query using eSQL...
Robin Rosen, Visits: 2, Total Bill: $1,221.36
Susan Kirby, Visits: 2, Total Bill: $4,036.80
Query using LINQ...
Robin Rosen, Visits: 2, Total Bill: $1,221.36
Susan Kirby, Visits: 2, Total Bill: $4,036.80
它是如何工作的?
我们先在模型里添加一个复杂类型,接着创建如Listing 11-11的GetVisitSummary() 函数,它能返回包含这个新建的复杂类型的集合.注意:复杂类型的构造函数接受参数的顺序要与我们定义它的属性时的顺序一致. 你可能需要检查.edmx文件,确保设计器按我们添加的顺序是一致.
由于我们函数返回IQueryable<VisitSummary>, 所以我们需要实现引导代码. 同样,因为我们需要访问ObjectContext的QueryProvider,所以我们需要在EFRecipesEntities类中实现运行时方法.
如果我们把这个函数用在LINQ查询中,你可能需要让这个方法返回IQueryable<DbDataRecord>给匿名类型.虽然我们的这个集合不能进一步过滤,但是包含复杂类型的集合是可以被进一点过滤的.