我正在尝试研究如何使用.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可能有数千个相关的Gizmo,在特定的请求中我只需要一次检索一个。不幸的是,上面的代码导致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演示了如何获取所有内容并过滤&#39;由实体对象的自然使用生成的方法。
上面的查询2,3和4都生成了我认为有效的SQL查询 - 一个返回单行并且在WHERE子句中有两个术语,但它们都涉及非常高跷的C#代码。
有没有人有一个解决方案可以编写自然的C#代码并在这种情况下生成有效的SQL利用率?
我应该注意到我已经尝试用我的Widget对象中的EntityCollection替换ICollection,以允许从上面的Query 2代码中删除强制转换。不幸的是,这会导致EntityException告诉我
无法将对象添加到EntityCollection或 的EntityReference。附加到ObjectContext的对象不能 被添加到不是的EntityCollection或EntityReference 与源对象相关联。
当我尝试检索任何相关对象时。
任何建议表示赞赏。
答案 0 :(得分:0)
好的,进一步的挖掘让我尽可能地接近我想要的位置(重申一下,这个代码看起来像OO但生成有效的数据库使用模式)。
事实证明,上面的Query2(将相关集合转换为EntityCollection
)实际上并不是一个好的解决方案,因为虽然它针对数据库生成了所需的查询类型,但仅仅是获取{{来自Gizmos
的集合足以使实体框架进入数据库并获取所有相关的Gizmo - 即执行我试图避免的查询。
但是,可以在不调用collection属性的getter的情况下获取关系widget
,如http://blogs.msdn.com/b/alexj/archive/2009/06/08/tip-24-how-to-get-the-objectcontext-from-an-entity.aspx所述。当您访问EntityCollection
集合属性时,此方法将回避实体框架获取相关实体。
因此,可以像这样添加Widget上的附加只读属性
Gizmos
然后调用代码看起来像这样
public IQueryable<Gizmo> GizmosQuery
{
get
{
var relationshipManager = ((IEntityWithRelationships)this).RelationshipManager;
return (IQueryable<Gizmo>) relationshipManager.GetAllRelatedEnds().First( x => x is EntityCollection<Gizmo>).CreateSourceQuery();
}
}
此方法生成的SQL可以有效地从数据库中只提取一行,但取决于以下条件保持为真
var g1 = widget.GizmosQuery.First(x => x.Name == "gizmo 99");
.First()
调用中需要更复杂的谓词。GizmosQuery
启用了代理创建,Widget类可以生成代理(https://msdn.microsoft.com/en-us/library/vstudio/dd468057%28v=vs.100%29.aspx)DbContext
新创建的对象上调用GizmosQuery属性,因为它们不是代理,不会实现new Widget()
。作为有效代理的新对象可以在必要时使用IEntityWithRelationships
创建。