使用Entity Framework时有哪些好的设计实践

ADB*_*ADB 74 asp.net entity-framework visual-studio-2008

这主要适用于不通过soa访问数据的asp.net应用程序.这意味着您可以访问从框架加载的对象,而不是转移对象,尽管某些建议仍然适用.

这是一个社区帖子,所以请在您认为合适时添加.

适用于:Visual Studio 2008 sp1附带的Entity Framework 1.0.

为什么首先选择EF?

考虑到这是一项存在大量问题的年轻技术(见下文),为您的项目加入EF可能很难.然而,这是微软正在推动的技术(以牺牲Linq2Sql为代价,它是EF的一个子集).此外,您可能对NHibernate或其他解决方案不满意.无论是什么原因,都有人(包括我)与EF合作,生活也不错.你想.

EF和继承

第一个重要的主题是继承.EF确实支持以两种方式持久化的继承类的映射:每个类的表和表的层次结构.建模很简单,该部分没有编程问题.

(以下适用于每个类模型的表,因为我没有每个层次结构的表的经验,无论如何,这是有限的.)当您尝试运行包含一个或多个对象的查询时,真正的问题就出现了.继承树:生成的sql非常糟糕,需要很长时间才能被EF解析并且需要很长时间才能执行.这是一个真正的表演塞子.足够的EF应该不能用于继承或尽可能少.

这是一个多么糟糕的例子.我的EF模型有~30个类,其中约10个是继承树的一部分.在运行查询以从Base类获取一个项目时,就像Base.Get(id)一样简单,生成的SQL超过50,000个字符.然后,当您尝试返回一些关联时,它会退化甚至更多,就抛出SQL异常而言,无法一次查询超过256个表.

好的,这很糟糕,EF概念允许您在没有(或尽可能少)考虑表的实际数据库实现的情况下创建对象结构.它完全失败了.

那么,推荐?如果可以的话,避免继承,性能会好得多.在必要的地方谨慎使用它.在我看来,这使得EF成为用于查询的美化sql生成工具,但使用它仍然有优势.以及实现类似于继承的机制的方法.

使用接口绕过继承

尝试使用EF进行某种继承时,首先要知道的是,您不能将非EF建模类指定为基类.甚至不尝试它,它将被建模者覆盖.那么该怎么办?

您可以使用接口来强制该类实现某些功能.例如,这里有一个IEntity接口,允许您定义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; } }
}
Run Code Online (Sandbox Code Playgroud)

使用此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);
    }
}
Run Code Online (Sandbox Code Playgroud)

它利用了一些扩展功能:

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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

不像普通继承那样整洁,特别是考虑到你必须将PetType存储在额外的数据库字段中,但考虑到性能提升,我不会回头看.

它也无法模拟一对多,多对多的关系,但是通过"联盟"的创造性使用,它可以起作用.最后,它创建了在对象的属性/函数中加载数据的侧面效果,您需要注意这一点.使用像GetXYZ()这样清晰的命名约定有助于解决这个问题.

编译查询

实体框架性能不如使用ADO(显然)或Linq2SQL直接访问数据库.但是有一些方法可以改进它,其中一种方法是编译你的查询.编译查询的性能类似于Linq2Sql.

什么是编译查询?它只是一个查询,您告诉框架将已解析的树保留在内存中,以便下次运行时不需要重新生成它.因此,下一次运行时,您将节省解析树所需的时间.不要打折,因为这是一个非常昂贵的操作,更复杂的查询会变得更糟.

编译查询有两种方法:使用EntitySQL创建ObjectQuery并使用CompiledQuery.Compile()函数.(请注意,通过在页面中使用EntityDataSource,您实际上将使用带有EntitySQL的ObjectQuery,以便进行编译和缓存).

在这里,如果您不知道EntitySQL是什么.它是一种基于字符串的方法,可以针对EF编写查询.下面是一个示例:"从Entities.DogSet中选择值dog作为狗,其中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();
Run Code Online (Sandbox Code Playgroud)

第一次运行此查询时,框架将生成表达式树并将其保留在内存中.因此,下次执行时,您将节省成本高昂的一步.在该示例中,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));
Run Code Online (Sandbox Code Playgroud)

或使用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());
Run Code Online (Sandbox Code Playgroud)

调用查询:

query_GetDog.Invoke( YourContext, id );
Run Code Online (Sandbox Code Playgroud)

The advantage of CompiledQuery is that the syntax of your query is checked at compile time, where as EntitySQL is not. However, there are other consideration...

Includes

Lets say you want to have the data for the dog owner to be returned by the query to avoid making 2 calls to the database. Easy to do, right?

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();
Run Code Online (Sandbox Code Playgroud)

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());
Run Code Online (Sandbox Code Playgroud)

Now, what if you want to have the Include parametrized? What I mean is that you want to have a single Get() function that is called from different pages that care about different relationships for the dog. One cares about the Owner, another about his FavoriteFood, another about his FavotireToy and so on. Basicly, you want to tell the query which associations to load.

It is easy to do with 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();
}
Run Code Online (Sandbox Code Playgroud)

The include simply uses the passed string. Easy enough. Note that it is possible to improve on the Include(string) function (that accepts only a single path) with an IncludeMany(string) that will let you pass a string of comma-separated associations to load. Look further in the extension section for this function.

If we try to do it with CompiledQuery however, we run into numerous problems:

The obvious

    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());
Run Code Online (Sandbox Code Playgroud)

will choke when called with:

query_GetDog.Invoke( YourContext, id, "Owner,FavoriteFood" );
Run Code Online (Sandbox Code Playgroud)

Because, as mentionned above, Include() only wants to see a single path in the string and here we are giving it 2: "Owner" and "FavoriteFood" (which is not to be confused with "Owner.FavoriteFood"!).

Then, let's use IncludeMany(), which is an extension function

    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());
Run Code Online (Sandbox Code Playgroud)

Wrong again, this time it is because the EF cannot parse IncludeMany because it is not part of the functions that is recognizes: it is an extension.

Ok, so you want to pass an arbitrary number of paths to your function and Includes() only takes a single one. What to do? You could decide that you will never ever need more than, say 20 Includes, and pass each separated strings in a struct to CompiledQuery. But now the query looks like this:

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
Run Code Online (Sandbox Code Playgroud)

which is awful as well. Ok, then, but wait a minute. Can't we return an ObjectQuery<> with CompiledQuery? Then set the includes on that? Well, that what I would have thought so as well:

    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;   
}
Run Code Online (Sandbox Code Playgroud)

That should have worked, except that when you call IncludeMany (or Include, Where, OrderBy...) you invalidate the cached compiled query because it is an entirely new one now! So, the expression tree needs to be reparsed and you get that performance hit again.

So what is the solution? You simply cannot use CompiledQueries with parametrized Includes. Use EntitySQL instead. This doesn't mean that there aren't uses for CompiledQueries. It is great for localized queries that will always be called in the same context. Ideally CompiledQuery should always be used because the syntax is checked at compile time, but due to limitation, that's not possible.

An example of use would be: you may want to have a page that queries which two dogs have the same favorite food, which is a bit narrow for a BusinessLayer function, so you put it in your page and know exactly what type of includes are required.

Passing more than 3 parameters to a CompiledQuery

Func is limited to 5 parameters, of which the last one is the return type and the first one is your Entities object from the model. So that leaves you with 3 parameters. A pitance, but it can be improved on very easily.

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();
}
Run Code Online (Sandbox Code Playgroud)

Return Types (this does not apply to EntitySQL queries as they aren't compiled at the same time during execution as the CompiledQuery method)

Working with Linq, you usually don't force the execution of the query until the very last moment, in case some other functions downstream wants to change the query in some way:

    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 );

}
Run Code Online (Sandbox Code Playgroud)

What is going to happen here? By still playing with the original ObjectQuery (that is the actual return type of the Linq statement, which implements IEnumerable), it will invalidate the compiled query and be force to re-parse. So, the rule of thumb is to return a List<> of objects instead.

    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 );

}
Run Code Online (Sandbox Code Playgroud)

When you call ToList(), the query gets executed as per the compiled query and then, later, the OrderBy is executed against the objects in memory. It may be a little bit slower, but I'm not even sure. One sure thing is that you have no worries about mis-handling the ObjectQuery and invalidating the compiled query plan.

Once again, that is not a blanket statement. ToList() is a defensive programming trick, but if you have a valid reason not to use ToList(), go ahead. There are many cases in which you would want to refine the query before executing it.

Performance

What is the performance impact of compiling a query? It can actually be fairly large. A rule of thumb is that compiling and caching the query for reuse takes at least double the time of simply executing it without caching. For complex queries (read inherirante), I have seen upwards to 10 seconds.

So, the first time a pre-compiled query gets called, you get a performance hit. After that first hit, performance is noticeably better than the same non-pre-compiled query. Practically the same as Linq2Sql

When you load a page with pre-compiled queries the first time you will get a hit. It will load in maybe 5-15 seconds (obviously more than one pre-compiled queries will end up being called), while subsequent loads will take less than 300ms. Dramatic difference, and it is up to you to decide if it is ok for your first user to take a hit or you want a script to call your pages to force a compilation of the queries.

Can this query be cached?

{
    Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog;
}
Run Code Online (Sandbox Code Playgroud)

No, ad-hoc Linq queries are not cached and you will incur the cost of generating the tree every single time you call it.

Parametrized Queries

Most search capabilities involve heavily parametrized queries. There are even libraries available that will let you build a parametrized query out of lamba expressions. The problem is that you cannot use pre-compiled queries with those. One way around that is to map out all the possible criteria in the query and flag which one you want to use:

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();
}
Run Code Online (Sandbox Code Playgroud)

The advantage here is that you get all the benifits of a pre-compiled quert. The disadvantages are that you most likely will end up with a where clause that is pretty difficult to maintain, that you will incur a bigger penalty for pre-compiling the query and that each query you run is not as efficient as it could be (particularly with joins thrown in).

Another way is to build an EntitySQL query piece by piece, like we all did with 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();
}
Run Code Online (Sandbox Code Playgroud)

Here the problems are: - there is no syntax checking during compilation - each different combination of parameters generate a different query which will need to be pre-compiled when it is first run. In this case, there are only 4 different possible queries (no params, age-only, name-only and both params), but you can see that there can be way more with a normal world search. - Noone likes to concatenate strings!

Another option is to query a large subset of the data and then narrow it down in memory. This is particularly useful if you are working with a definite subset of the data, like all the dogs in a city. You know there are a lot but you also know there aren't that many... so your CityDog search page can load all the dogs for the city in memory, which is a single pre-compiled query and then refine the results

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;
}
Run Code Online (Sandbox Code Playgroud)

It is particularly useful when you start displaying all the data then allow for filtering.

Problems: - Could lead to serious data transfer if you are not careful about your subset. - You can only filter on the data that you returned. It means that if you don't return the Dog.Owner association, you will not be able to filter on the Dog.Owner.Name So what is the best solution? There isn't any. You need to pick the solution that works best for you and your problem: - Use lambda-based query building when you don't care about pre-compiling your queries. - Use fully-defined pre-compiled Linq query when your object structure is not too complex. - Use EntitySQL/string concatenation when the structure could be complex and when the possible number of different resulting queries are small (which means fewer pre-compilation hits). - Use in-memory filtering when you are working with a smallish subset of the data or when you had to fetch all of the data on the data at first anyway (if the performance is fine with all the data, then filtering in memory will not cause any time to be spent in the db).

Singleton access

The best way to deal with your context and entities accross all your pages is to use the singleton pattern:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

NoTracking, is it worth it?

When executing a query, you can tell the framework to track the objects it will return or not. What does it mean? With tracking enabled (the default option), the framework will track what is going on with the object (has it been modified? Created? Deleted?) and will also link objects together, when further queries are made from the database, which is what is of interest here.

For example, lets assume that Dog with ID == 2 has an owner which 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;
Run Code Online (Sandbox Code Playgroud)

If we were to do the same with no tracking, the result would be different.

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;
Run Code Online (Sandbox Code Playgroud)

Tracking is very useful and in a perfect world without performance issue, it would always be on. But in this world, there is a price for it, in terms of performance. So, should you use NoTracking to speed things up? It depends on what you are planning to use the data for.

Is there any chance that the data your query with NoTracking can be used to make update/insert/delete in the database? If so, don't use NoTracking because associations are not tracked and will causes exceptions to be thrown.

In a page where there are absolutly no updates to the database, you can use NoTracking.

Mixing tracking and NoTracking is possible, but it requires you to be extra careful with updates/inserts/deletes. The problem is that if you mix then you risk having the framework trying to Attach() a NoTracking object to the context where another copy of the same object exist with tracking on. Basicly, what I am saying is that

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();
Run Code Online (Sandbox Code Playgroud)

dog1 and dog2 are 2 different objects, one tracked and one not. Using the detached object in an update/insert will force an Attach() that will say "Wait a minute, I do already have an object here with the same database key. Fail". And when you Attach() one object, all of its hierarchy gets attached as well, causing problems everywhere. Be extra careful.

How much faster is it with NoTracking

It depends on the queries. Some are much more succeptible to tracking than other. I don't have a fast an easy rule for it, but it helps.

So I should use NoTracking everywhere then?

Not exactly. There are some advantages to tracking object. The first one is that the object is cached, so subsequent call for that object will not hit the database. That cache is only valid for the lifetime of the YourEntities object, which, if you use the singleton code above, is the same as the page lifetime. One page request == one YourEntity object. So for multiple calls for the same object, it will load only once per page request. (Other caching mechanism could extend that).

What happens when you are using NoTracking and try to load the same object multiple times? The database will be queried each time, so there is an impact there. How often do/should you call for the same object during a single page request? As little as possible of course, but it does happens.

Also remember the piece above about having the associations connected automatically for your? You don't have that with NoTracking, so if you load your data in multiple batches, you will not have a link to between them:

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();
Run Code Online (Sandbox Code Playgroud)

In this case, no dog will have its .Owner property set.

Some things to keep in mind when you are trying to optimize the performance.

No lazy loading, what am I to do?

This can be seen as a blessing in disguise. Of course it is annoying to load everything manually. However, it decreases the number of calls to the db and forces you to think about when you should load data. The more you can load in one database call the better. That was always true, but it is enforced now with this 'feature' of EF.

Of course, you can call if( !ObjectReference.IsLoaded ) ObjectReference.Load(); if you want to, but a better practice is to force the framework to load the objects you know you will need in one shot. This is where the discussion about parametrized Includes begins to make sense.

Lets say you have you Dog object

public class Dog
{
    public Dog Get(int id)
    {
        return YourContext.DogSet.FirstOrDefault(it => it.ID == id );
    }
}
Run Code Online (Sandbox Code Playgroud)

This is the type of function you work with all the time. It gets called from all over the place and once you have that Dog object, you will do very different things to it in different functions. First, it should be pre-compiled, because you will call that very often. Second, each different pages will want to have access to a different subset of the Dog data. Some will want the Owner, some the FavoriteToy, etc.

Of course, you could call Load() for each reference you need anytime you need one. But that will generate a call to the database each time. Bad idea. So instead, each page will ask for the data it wants to see when it first request for the Dog object:

    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 " +
Run Code Online (Sandbox Code Playgroud)

use*_*545 1

虽然信息丰富,但我认为分享所有这些如何融入完整的解决方案架构可能会更有帮助。示例 - 获得一个解决方案,显示您在何处使用 EF 继承和替代方案,以便显示它们的性能差异。