如何使EntityFramework为相关对象生成高效的SQL查询?
本文关键字:高效 SQL 查询 对象 EntityFramework 何使 | 更新日期: 2023-09-27 17:54:46
我试图找出如何使用。net EntityFramework生成可读和自然代码和高效的SQL查询语句时获取相关实体。例如,给定以下代码优先定义
public class WidgetContext : DbContext
{
public DbSet<Widget> Widgets { get; set; }
public DbSet<Gizmo> Gizmos { get; set; }
}
public class Widget
{
public virtual int Id { get; set; }
[Index]
[MaxLength(512)]
public virtual string Name { get; set; }
public virtual ICollection<Gizmo> Gizmos { get; set; }
}
public class Gizmo
{
public virtual long Id { get; set; }
[Index]
[MaxLength(512)]
public virtual string Name { get; set; }
public virtual Widget Widget { get; set; }
public virtual int WidgetId { get; set; }
}
我希望能够编写像
这样的代码using (var wc = new WidgetContext())
{
var widget = wc.Widgets.First(x => x.Id == 123);
var gizmo = widget.Gizmos.First(x => x.Name == "gizmo 99");
}
并查看沿着
行创建的SQL查询SELECT TOP (1) * from Gizmos WHERE WidgetId = 123 AND Name = 'gizmo 99'
这样,选择正确的Gizmo的工作是由数据库执行的。这很重要,因为在我的用例中,每个Widget可能有数千个相关的Gizmos,而在一个特定的请求中,我一次只需要检索一个。不幸的是,上面的代码导致EntityFramework创建像这样的SQL
SELECT * from Gizmos WHERE WidgetId = 123
Gizmo上的匹配。然后通过扫描相关Gizmo实体的完整集合在内存中执行Name。
经过大量的实验,我已经找到了在实体框架中创建高效SQL使用的方法,但只有通过使用不太自然编写的丑陋代码。下面的例子说明了这一点。
using System.Data.Entity;
using System.Data.Entity.Core.Objects.DataClasses;
using System.Linq;
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<WidgetContext>());
using (var wc = new WidgetContext())
{
var widget = new Widget() { Name = "my widget"};
wc.Widgets.Add(widget);
wc.SaveChanges();
}
using (var wc = new WidgetContext())
{
var widget = wc.Widgets.First();
for (int i = 0; i < 1000; i++)
widget.Gizmos.Add(new Gizmo() { Name = string.Format("gizmo {0}", i) });
wc.SaveChanges();
}
using (var wc = new WidgetContext())
{
wc.Database.Log = Console.WriteLine;
var widget = wc.Widgets.First();
Console.WriteLine("=====> Query 1");
// queries all gizmos associated with the widget and then runs the 'First' query in memory. Nice code, ugly database usage
var g1 = widget.Gizmos.First(x => x.Name == "gizmo 99");
Console.WriteLine("=====> Query 2");
// queries on the DB with two terms in the WHERE clause - only pulls one record, good SQL, ugly code
var g2 = ((EntityCollection<Gizmo>) widget.Gizmos).CreateSourceQuery().First(x => x.Name == "gizmo 99");
Console.WriteLine("=====> Query 3");
// queries on the DB with two terms in the WHERE clause - only pulls one record, good SQL, ugly code
var g3 = wc.Gizmos.First(x => x.Name == "gizmo 99" && x.WidgetId == widget.Id);
Console.WriteLine("=====> Query 4");
// queries on the DB with two terms in the WHERE clause - only pulls one record, also good SQL, ugly code
var g4 = wc.Entry(widget).Collection(x => x.Gizmos).Query().First(x => x.Name == "gizmo 99");
}
Console.ReadLine();
}
查询1演示了通过实体对象的自然使用生成的"获取所有内容并过滤"方法。
上面的查询2,3和4都生成了我认为是高效的SQL查询-一个返回单行并且在WHERE子句中有两个术语的查询,但是它们都涉及非常生硬的c#代码。
有没有人有一个解决方案,将允许编写自然的c#代码,并在这种情况下生成高效的SQL利用率?我应该注意到,我已经尝试用我的Widget对象中的EntityCollection替换iccollection,以允许从上面的Query 2代码中删除强制转换。不幸的是,这会导致EntityException告诉我
对象无法添加到EntityCollection或EntityReference。附加到ObjectContext的对象则不能将被添加到不存在的EntityCollection或EntityReference中与源对象相关联。
当我试图检索任何相关对象时。
好的,进一步的挖掘让我尽可能接近我想要的地方(重申一下,这是看起来OO但生成高效DB使用模式的代码)。
事实证明,上面的Query2(将相关集合转换为EntityCollection
)实际上不是一个很好的解决方案,因为尽管它对数据库生成了所需的查询类型,但仅仅从widget
中获取Gizmos
集合的行为就足以使实体框架离开数据库并获取所有相关的Gizmos -即执行我试图避免的查询。
但是,可以在不调用集合属性的getter的情况下获得关系的EntityCollection
,如下面http://blogs.msdn.com/b/alexj/archive/2009/06/08/tip-24-how-to-get-the-objectcontext-from-an-entity.aspx所述。当你访问Gizmos
集合属性时,这种方法避免了实体框架获取相关实体。
因此,可以像这样在小部件上添加一个额外的只读属性
public IQueryable<Gizmo> GizmosQuery
{
get
{
var relationshipManager = ((IEntityWithRelationships)this).RelationshipManager;
return (IQueryable<Gizmo>) relationshipManager.GetAllRelatedEnds().First( x => x is EntityCollection<Gizmo>).CreateSourceQuery();
}
}
然后调用代码可以像这样
var g1 = widget.GizmosQuery.First(x => x.Name == "gizmo 99");
这种方法生成的SQL只有效地从数据库中获取单行,但取决于以下条件是否为真
- 从源类型到目标类型只有一个关系。如果有多个关系将Widget链接到Gizmos,则意味着在
GizmosQuery
中的.First()
调用中将需要更复杂的谓词。 - 为
DbContext
启用代理创建,并且Widget类有资格生成代理(https://msdn.microsoft.com/en-us/library/vstudio/dd468057%28v=vs.100%29.aspx) - GizmosQuery属性不能在使用
new Widget()
新创建的对象上调用,因为这些对象不是代理,也不会实现IEntityWithRelationships
。如果有必要,可以使用wc.Widgets.Create()
创建有效代理的新对象。