使用entity framework时,什么是良好的devise实践?
这将主要适用于不通过soa访问数据的asp.net应用程序。 这意味着你可以访问从框架加载的对象,而不是传输对象,尽pipe一些build议仍然适用。
这是一个社区post,所以请根据您的情况补充。
适用于 :Visual Studio 2008 sp1附带的entity framework1.0。
为什么selectEF呢?
考虑到这是一个年轻的技术,有很多问题(见下文),可能是一个很难卖你的项目EF EF的行列。 但是,这是微软推动的技术(以Linq2Sql为代表的EF子集)。 另外,你可能不满意NHibernate或其他解决scheme。 不pipe是什么原因,有人在那里(包括我)和EF一起工作,生活也不坏。让你想想。
EF和inheritance
第一大主题是inheritance。 EF确实支持以两种方式持久化的inheritance类的映射:每个类的表和表的层次结构。 build模很容易,而且这个部分没有编程问题。
(以下内容适用于每个类模型的表,因为我没有每个层次表的经验,无论如何,这是有限的)。当您试图运行包含一个或多个对象的查询一个inheritance树:生成的sql非常糟糕,需要很长时间才能被EFparsing并且需要很长时间才能执行。 这是一个真正的表演塞。 足够的EF可能不应该用inheritance或尽可能less。
这是一个很糟糕的例子。 我的EF模型有30个类,其中约10个是inheritance树的一部分。 在运行查询以从Base类获取一个项目(如Base.Get(id))时,生成的SQL超过了50,000个字符。 然后当你试图返回一些关联时,它会退化得更多,甚至会抛出SQLexception一次不能查询超过256个表。
好吧,这是不好的,EF的概念是允许你创build你的对象结构没有(或尽可能less)考虑你的表的实际数据库实现。 这完全失败了。
那么,build议? 如果可以,避免inheritance,性能会好很多。 在必要的地方谨慎使用它。 在我看来,这使得EF成为查询的一个光荣的sql生成工具,但是使用它仍然有优势。 以及如何实现类似于inheritance的机制。
用Interfaces绕过inheritance
试图获得与EF一起进行某种types的inheritance时,首先要了解的是,不能将一个非EF模型化的类分配给一个基类。 不要尝试它,它会被build模者覆盖。 那么该怎么办?
您可以使用接口来强制这些类实现一些function。 例如,这里是一个IEntity接口,它允许您定义在devise时不知道实体types的EF实体之间的关联。
public enum EntityTypes{ Unknown = -1, Dog = 0, Cat } public interface IEntity { int EntityID { get; } string Name { get; } Type EntityType { get; } } public partial class Dog : IEntity { // implement EntityID and Name which could actually be fields // from your EF model Type EntityType{ get{ return EntityTypes.Dog; } } }
使用这个IEntity,你可以在其他类中使用未定义的关联
// lets take a class that you defined in your model. // that class has a mapping to the columns: PetID, PetType public partial class Person { public IEntity GetPet() { return IEntityController.Get(PetID,PetType); } }
它使用了一些扩展function:
public class IEntityController { static public IEntity Get(int id, EntityTypes type) { switch (type) { case EntityTypes.Dog: return Dog.Get(id); case EntityTypes.Cat: return Cat.Get(id); default: throw new Exception("Invalid EntityType"); } } }
不像纯粹的inheritance一样简洁,特别是考虑到你必须将PetType存储在一个额外的数据库字段中,但考虑到性能增益,我不会回头。
它也不能模拟一对多,多对多的关系,但是通过“联盟”的创造性使用,它可以起作用。 最后,它会在对象的属性/函数中创build加载数据的副作用,您需要小心。 使用像GetXYZ()这样清晰的命名约定有助于这方面的工作。
编译查询
entity framework的性能不如使用ADO(显然)或Linq2SQL的直接数据库访问。 有办法改善它,但其中之一是编译您的查询。 编译查询的性能与Linq2Sql类似。
什么是编译查询? 这只是一个查询,告诉框架将分析的树保存在内存中,以便在下次运行时不需要重新生成。 所以下一次运行,您将节省parsing树的时间。 不要打折,因为这是一个非常昂贵的操作,更复杂的查询变得更糟糕。
有两种编译查询的方法:使用EntitySQL创buildObjectQuery并使用CompiledQuery.Compile()函数。 (请注意,通过在页面中使用EntityDataSource,实际上将使用EntitySQL中的ObjectQuery,以便进行编译和caching)。
如果你不知道EntitySQL是什么,在这里旁边。 这是一种基于string的方式来写入对EF的查询。 这里是一个例子:“从Entities.DogSet中select值狗作为狗,其中dog.ID = @ID”。 该语法与SQL语法非常相似。 你也可以做相当复杂的对象操作,这很好的解释了[这里] [1]。
好吧,这里是如何使用ObjectQuery <>来完成的
string query = "select value dog " + "from Entities.DogSet as dog " + "where dog.ID = @ID"; ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault();
第一次运行这个查询时,框架会生成expression式树并保存在内存中。 所以下一次执行时,您将节省昂贵的一步。 在这个例子中,EnablePlanCaching = true,这是不必要的,因为这是默认选项。
另一种编译查询以供以后使用的方法是CompiledQuery.Compile方法。 这使用一个委托:
static readonly Func<Entities, int, Dog> query_GetDog = CompiledQuery.Compile<Entities, int, Dog>((ctx, id) => ctx.DogSet.FirstOrDefault(it => it.ID == id));
或者使用linq
static readonly Func<Entities, int, Dog> query_GetDog = CompiledQuery.Compile<Entities, int, Dog>((ctx, id) => (from dog in ctx.DogSet where dog.ID == id select dog).FirstOrDefault());
调用查询:
query_GetDog.Invoke( YourContext, id );
CompiledQuery的优点是在编译时检查查询的语法,EntitySQL不是。 但是,还有其他的考虑…
包括
假设你想让查询返回狗所有者的数据,以避免对数据库进行2次调用。 容易做,对吗?
EntitySQL
string query = "select value dog " + "from Entities.DogSet as dog " + "where dog.ID = @ID"; ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)).Include("Owner"); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault();
CompiledQuery
static readonly Func<Entities, int, Dog> query_GetDog = CompiledQuery.Compile<Entities, int, Dog>((ctx, id) => (from dog in ctx.DogSet.Include("Owner") where dog.ID == id select dog).FirstOrDefault());
现在,如果你想要包含参数化? 我的意思是,你想有一个单独的Get()函数,它是从不同的页面调用的,这些页面关心的是不同的关系。 一个关心所有者,另一个关于他的FavoriteFood,另一个关于他的FavotireToy等等。 基本上,你想告诉查询加载哪些关联。
使用EntitySQL很容易
public Dog Get(int id, string include) { string query = "select value dog " + "from Entities.DogSet as dog " + "where dog.ID = @ID"; ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)) .IncludeMany(include); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault(); }
包括只是使用传递的string。 很简单。 请注意,可以使用IncludeMany(string)改进Include(string)函数(仅接受单一path),这将允许您传递一串逗号分隔的关联来加载。 进一步查看该function的扩展部分。
如果我们试图用CompiledQuery来做到这一点,我们遇到了许多问题:
显而易见的
static readonly Func<Entities, int, string, Dog> query_GetDog = CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) => (from dog in ctx.DogSet.Include(include) where dog.ID == id select dog).FirstOrDefault());
当用以下方式呼叫时会窒息:
query_GetDog.Invoke( YourContext, id, "Owner,FavoriteFood" );
因为,如上所述,Include()只想在string中看到一个单独的path,在这里我们给它2:“Owner”和“FavoriteFood”(不要和“Owner.FavoriteFood”混淆!
然后,我们使用IncludeMany(),这是一个扩展函数
static readonly Func<Entities, int, string, Dog> query_GetDog = CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) => (from dog in ctx.DogSet.IncludeMany(include) where dog.ID == id select dog).FirstOrDefault());
又错了,这次是因为EF无法parsingIncludeMany,因为它不是被识别的函数的一部分:它是一个扩展。
好的,所以你想传递任意数量的path到你的函数,而Includes()只有一个。 该怎么办? 你可以决定永远不需要比包含更多的东西,并且把结构中的每个分隔string传递给CompiledQuery。 但现在查询看起来像这样:
from dog in ctx.DogSet.Include(include1).Include(include2).Include(include3) .Include(include4).Include(include5).Include(include6) .[...].Include(include19).Include(include20) where dog.ID == id select dog
这也很糟糕。 好,那就等一下。 我们不能用CompiledQuery返回一个ObjectQuery <>吗? 然后设置包括呢? 那么,我也会这样想:
static readonly Func<Entities, int, ObjectQuery<Dog>> query_GetDog = CompiledQuery.Compile<Entities, int, string, ObjectQuery<Dog>>((ctx, id) => (ObjectQuery<Dog>)(from dog in ctx.DogSet where dog.ID == id select dog)); public Dog GetDog( int id, string include ) { ObjectQuery<Dog> oQuery = query_GetDog(id); oQuery = oQuery.IncludeMany(include); return oQuery.FirstOrDefault; }
除了当你调用IncludeMany(或者Include,Where,OrderBy …)时,你应该已经工作了,因为它是一个全新的caching编译查询。 因此,expression式树需要重新设置,并且您再次获得了性能。
那么解决scheme是什么? 你只是不能使用CompiledQueries参数化的包括。 改用EntitySQL。 这并不意味着没有使用CompiledQueries。 对于总是在相同的上下文中调用的本地化查询来说非常好。 理想情况下应该总是使用CompiledQuery,因为在编译时检查语法,但是由于限制,这是不可能的。
一个使用的例子是:你可能想要一个页面来查询哪两条狗有相同的最喜欢的食物,这对于一个BusinessLayer函数来说有点狭窄,所以你把它放在你的页面中,并且确切地知道包括什么types需要。
将3个以上的parameter passing给CompiledQuery
Func限于5个参数,其中最后一个是返回types,第一个参数是模型中的实体对象。 所以这留下了3个参数。 但是,它可以很容易地改进。
public struct MyParams { public string param1; public int param2; public DateTime param3; } static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog = CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((ctx, myParams) => from dog in ctx.DogSet where dog.Age == myParams.param2 && dog.Name == myParams.param1 and dog.BirthDate > myParams.param3 select dog); public List<Dog> GetSomeDogs( int age, string Name, DateTime birthDate ) { MyParams myParams = new MyParams(); myParams.param1 = name; myParams.param2 = age; myParams.param3 = birthDate; return query_GetDog(YourContext,myParams).ToList(); }
返回types(这不适用于EntitySQL查询,因为它们在CompiledQuery方法的执行过程中不会在同一时间编译)
使用Linq,通常不会强制查询的执行直到最后一刻,以防下游的某些其他函数想要以某种方式更改查询:
static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog = CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) => from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog); public IEnumerable<Dog> GetSomeDogs( int age, string name ) { return query_GetDog(YourContext,age,name); } public void DataBindStuff() { IEnumerable<Dog> dogs = GetSomeDogs(4,"Bud"); // but I want the dogs ordered by BirthDate gridView.DataSource = dogs.OrderBy( it => it.BirthDate ); }
这里会发生什么? 通过仍然使用原始ObjectQuery(即实现IEnumerable的Linq语句的实际返回types),它将使已编译的查询无效,并强制重新parsing。 所以,经验法则是返回一个List <>的对象。
static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog = CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) => from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog); public List<Dog> GetSomeDogs( int age, string name ) { return query_GetDog(YourContext,age,name).ToList(); //<== change here } public void DataBindStuff() { List<Dog> dogs = GetSomeDogs(4,"Bud"); // but I want the dogs ordered by BirthDate gridView.DataSource = dogs.OrderBy( it => it.BirthDate ); }
当您调用ToList()时,将根据编译后的查询执行查询,然后,将针对内存中的对象执行OrderBy。 这可能会慢一点,但我甚至不确定。 一件确定的事情是,您不必担心error handlingObjectQuery并使已编译的查询计划无效。
再一次,这不是一个全面的说法。 ToList()是一个防御性的编程技巧,但是如果你有一个合理的理由不使用ToList(),那么继续。 在执行查询之前,有很多情况下需要优化查询。
性能
编译查询的性能影响是什么? 它实际上可以相当大。 一个经验法则是编译和caching查询重用需要至less两倍的时间简单地执行它没有caching。 对于复杂的查询(阅读inherirante),我已经看到了10秒。
所以,第一次调用预编译的查询时,性能会受到影响。 在第一次点击之后,性能明显优于相同的非预编译查询。 几乎和Linq2Sql一样
当您第一次使用预编译查询加载页面时,您将会遇到问题。 它可能会加载5-15秒(显然多个预编译的查询将最终被调用),而后续的加载将不到300毫秒。 戏剧性的差异,你决定是否可以让你的第一个用户点击或者你想要一个脚本调用你的页面来强制查询的编译。
这个查询可以被caching吗?
{ Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog; }
不,临时Linq查询没有被caching,并且每次调用它时都会产生生成树的代价。
参数化查询
大多数searchfunction涉及严重的参数化查询。 甚至有可用的库,可以让你build立一个参数化的查询从lambaexpression式。 问题是你不能使用这些预编译的查询。 解决这个问题的一个方法是在查询中映射出所有可能的标准,并标记你想使用哪一个:
public struct MyParams { public string name; public bool checkName; public int age; public bool checkAge; } static readonly Func<Entities, MyParams, IEnumerable<Dog>> query_GetDog = CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((ctx, myParams) => from dog in ctx.DogSet where (myParams.checkAge == true && dog.Age == myParams.age) && (myParams.checkName == true && dog.Name == myParams.name ) select dog); protected List<Dog> GetSomeDogs() { MyParams myParams = new MyParams(); myParams.name = "Bud"; myParams.checkName = true; myParams.age = 0; myParams.checkAge = false; return query_GetDog(YourContext,myParams).ToList(); }
这里的好处是你可以获得预编译的quert的所有好处。 缺点是你很可能最终会得到一个很难维护的where子句,所以你将会对预编译查询产生更大的惩罚,并且你所运行的每个查询都不是那么有效(尤其是与join投入)。
另一种方法是逐个构build一个EntitySQL查询,就像我们用SQL所做的一样。
protected List<Dod> GetSomeDogs( string name, int age) { string query = "select value dog from Entities.DogSet where 1 = 1 "; if( !String.IsNullOrEmpty(name) ) query = query + " and dog.Name == @Name "; if( age > 0 ) query = query + " and dog.Age == @Age "; ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>( query, YourContext ); if( !String.IsNullOrEmpty(name) ) oQuery.Parameters.Add( new ObjectParameter( "Name", name ) ); if( age > 0 ) oQuery.Parameters.Add( new ObjectParameter( "Age", age ) ); return oQuery.ToList(); }
这里的问题是: – 在编译过程中没有语法检查 – 每个不同的参数组合都会生成一个不同的查询,在第一次运行时需要预编译。 在这种情况下,只有4种可能的查询(没有参数,只有年龄,只有名称和两个参数),但是你可以看到在正常的世界search中可以有更多的方法。 – 没有人喜欢连接string!
另一个select是查询数据的一个大的子集,然后将其缩小到内存中。 如果您正在处理一个明确的数据子集,就像城市中的所有狗一样,这一点尤其有用。 你知道有很多,但你也知道没有那么多…所以你的CityDogsearch页面可以在内存中加载城市的所有狗,这是一个预编译的查询,然后提炼结果
protected List<Dod> GetSomeDogs( string name, int age, string city) { string query = "select value dog from Entities.DogSet where dog.Owner.Address.City == @City "; ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>( query, YourContext ); oQuery.Parameters.Add( new ObjectParameter( "City", city ) ); List<Dog> dogs = oQuery.ToList(); if( !String.IsNullOrEmpty(name) ) dogs = dogs.Where( it => it.Name == name ); if( age > 0 ) dogs = dogs.Where( it => it.Age == age ); return dogs; }
当你开始显示所有数据,然后允许过滤时,这是特别有用的。
问题: – 如果你不注意你的子集,可能导致严重的数据传输。 – 您只能过滤返回的数据。 这意味着如果您不返回Dog.Owner关联,您将无法过滤Dog.Owner.Name那么最好的解决scheme是什么? 没有任何 您需要select最适合您和您的问题的解决scheme: – 当您不关心预编译查询时,使用基于lambda的查询构build。 – 当你的对象结构不太复杂时,使用完全定义的预编译的Linq查询。 – 当结构可能比较复杂时,以及当不同的结果查询的可能数目很小(这意味着更less的预编译命中)时,使用EntitySQL /string连接。 – 在处理一小部分数据时,或者首先必须首先获取数据的所有数据(如果所有数据的性能都正常,那么在内存中过滤将不会导致任何时间都花在分贝)。
单身访问
处理所有页面上下文和实体的最佳方式是使用单例模式:
public sealed class YourContext { private const string instanceKey = "On3GoModelKey"; YourContext(){} public static YourEntities Instance { get { HttpContext context = HttpContext.Current; if( context == null ) return Nested.instance; if (context.Items[instanceKey] == null) { On3GoEntities entity = new On3GoEntities(); context.Items[instanceKey] = entity; } return (YourEntities)context.Items[instanceKey]; } } class Nested { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Nested() { } internal static readonly YourEntities instance = new YourEntities(); } }
NoTracking,这是值得的吗?
当执行一个查询时,你可以告诉框架跟踪它将返回或不返回的对象。 这是什么意思? 启用跟踪function(默认选项)后,框架将跟踪对象的情况(被修改?创build?删除?),并且还将链接对象,当从数据库进一步查询时,这是什么这里很有意思。
例如,假设ID = 2的Dog拥有一个ID == 10的所有者。
Dog dog = (from dog in YourContext.DogSet where dog.ID == 2 select dog).FirstOrDefault(); //dog.OwnerReference.IsLoaded == false; Person owner = (from o in YourContext.PersonSet where o.ID == 10 select dog).FirstOrDefault(); //dog.OwnerReference.IsLoaded == true;
如果我们在没有跟踪的情况下做同样的事情,结果会不一样。
ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>) (from dog in YourContext.DogSet where dog.ID == 2 select dog); oDogQuery.MergeOption = MergeOption.NoTracking; Dog dog = oDogQuery.FirstOrDefault(); //dog.OwnerReference.IsLoaded == false; ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>) (from o in YourContext.PersonSet where o.ID == 10 select o); oPersonQuery.MergeOption = MergeOption.NoTracking; Owner owner = oPersonQuery.FirstOrDefault(); //dog.OwnerReference.IsLoaded == false;
跟踪是非常有用的,在完美的世界没有性能问题,它会一直在。 但在这个世界上,就性能而言,它是有代价的。 那么,你应该使用NoTracking来加快速度吗? 这取决于你计划使用的数据。
有没有可能使用NoTracking查询的数据在数据库中进行更新/插入/删除? 如果是这样,不要使用NoTracking,因为没有跟踪关联,并会导致抛出exception。
在绝对没有数据库更新的页面中,可以使用NoTracking。
混合跟踪和NoTracking是可能的,但它需要您更新/插入/删除额外小心。 问题是,如果你混合使用,那么你可能会冒险让框架尝试Attach()一个NoTracking对象到另一个相同对象的副本存在的上下文。 基本上,我说的是
Dog dog1 = (from dog in YourContext.DogSet where dog.ID == 2).FirstOrDefault(); ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>) (from dog in YourContext.DogSet where dog.ID == 2 select dog); oDogQuery.MergeOption = MergeOption.NoTracking; Dog dog2 = oDogQuery.FirstOrDefault();
dog1和dog2是两个不同的对象,一个跟踪,一个不是。 在更新/插入中使用detached对象会强制一个Attach(),它会说“等一下,我已经有一个对象在这里,使用相同的数据库键。 当Attach()一个对象时,它的所有层次结构都会被连接,从而导致无处不在的问题。 要特别小心。
NoTracking速度快了多less
这取决于查询。 有些人比其他人更容易追踪。 我没有一个简单的规则,但它有帮助。
那么我应该到处使用NoTracking?
不完全是。 跟踪对象有一些优点。 第一个是对象被caching,所以后续调用该对象不会碰到数据库。 该caching仅对于YourEntities对象的生命周期有效,如果使用上面的单例代码,则与页面生存期相同。 一个页面请求==一个YourEntity对象。 所以对于同一个对象的多次调用,每个页面请求只会加载一次。 (其他caching机制可以扩展)。
当您使用NoTracking并尝试多次加载相同的对象时会发生什么? 数据库每次都会被查询,所以会有影响。 在单个页面请求期间,您应该多长时间一次调用同一个对象? 当然,尽可能less,但确实发生。
还记得上面有关于自动连接你的? 你没有跟踪NoTracking,所以如果你加载多个批次的数据,你将不会有一个链接到他们之间:
ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)(from dog in YourContext.DogSet select dog); oDogQuery.MergeOption = MergeOption.NoTracking; List<Dog> dogs = oDogQuery.ToList(); ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)(from o in YourContext.PersonSet select o); oPersonQuery.MergeOption = MergeOption.NoTracking; List<Person> owners = oPersonQuery.ToList();
在这种情况下,没有狗会设置.Owner属性。
当您尝试优化性能时,请记住一些事项。
没有懒加载,我该怎么办?
这可以被看作是变相的祝福。 当然,手动加载所有东西都是很烦人的。 但是,它会减less对db的调用次数,并迫使您在加载数据时考虑。 您可以在一个数据库中加载得越多,就越好。 这一直是真实的,但现在EF的这个“特征”被强制执行了。
当然,你可以调用if(!ObjectReference.IsLoaded)ObjectReference.Load(); 如果你想,但更好的做法是强制框架加载你知道你将需要在一个镜头的对象。 这是关于参数化包含的讨论开始有意义的地方。
可以说你有你的狗对象
public class Dog { public Dog Get(int id) { return YourContext.DogSet.FirstOrDefault(it => it.ID == id ); } }
这是你一直工作的functiontypes。 它被从所有的地方调用,一旦你有了这个Dog对象,你将在不同的function中完成不同的事情。 首先,它应该是预编译的,因为你会经常这么叫。 其次,每个不同的页面将希望访问Dog数据的不同子集。 有些人会想要所有者,一些FavoriteToy,等等
当然,您可以随时调用Load()来获取每个需要的引用。 但是每次都会生成对数据库的调用。 馊主意。 相反,每个页面在第一次请求Dog对象时都会询问它想要看到的数据:
static public Dog Get(int id) { return GetDog(entity,"");} static public Dog Get(int id, string includePath) { string query = "select value o " + " from YourEntities.DogSet as o " +
请不要使用以上所有的信息,如“单身访问”。 你绝对100%不应该存储这个上下文重用,因为它不是线程安全的。
虽然信息丰富,但我认为分享如何将这些融合到一个完整的解决scheme架构中可能会更有帮助。 示例 – 得到一个解决scheme,显示您在哪里使用EFinheritance和您的替代scheme,以显示其性能差异。