使用Entity Framework时的良好设计实践是什么?

74

对于不通过 SOA 访问数据的 asp.net 应用程序来说,这将主要适用于从框架中加载的对象而不是传输对象,尽管仍有一些建议适用。

这是一个社区贴子,请根据需要添加内容。

适用于:Entity Framework 1.0(附带 Visual Studio 2008 sp1)。

为什么首选 EF?

考虑到它是一个年轻的技术,存在很多问题(见下文),可能很难让您的项目使用 EF。但是,这是微软正在推动的技术(以 Linq2Sql 为代价,Linq2Sql 是 EF 的子集)。此外,您可能对 NHibernate 或其他解决方案不满意。无论原因如何,有人(包括我在内)正在使用 EF,生活并不糟糕。

EF 和继承

第一个重要主题是继承。EF 支持映射持久化的继承类的两种方式:按类和按层次结构划分表。建模很容易,这部分没有编程问题。

(以下适用于每个类模型,因为我没有使用过每个层次结构表,而且其使用也受限于某些因素。)当您尝试运行包括属于继承树中一个或多个对象的查询时,真正的问题就出现了:生成的 SQL 非常糟糕,EF 要花很长时间对其进行解析,执行时间也很长。这是一个真正的阻碍。因此,EF 可能不应该使用继承,或者尽可能少用。

以下是它有多糟糕的一个例子。我的 EF 模型有大约 30 个类,其中约有 10 个是继承树的一部分。运行从基类获取一个项的查询,如 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; } }
}

使用此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);
    }
}

使用了一些扩展函数:

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

虽然不如普通继承优雅,特别是考虑到你需要在额外的数据库字段中存储PetType,但鉴于性能提升,我不会后悔选择这种方式。

它也无法建模一对多、多对多关系,但通过使用“Union”的创造性用法,可以使其正常工作。最后,它会在对象的属性/函数中加载数据,产生副作用,因此您需要小心处理。使用清晰的命名约定,例如GetXYZ()有助于解决这个问题。

编译查询

与直接使用ADO(显然)或Linq2SQL访问数据库相比,Entity Framework的性能不佳。但是有方法可以改善性能,其中之一就是编译查询。编译查询的性能类似于Linq2Sql。

什么是编译查询?它只是一个查询,在其中告诉框架将解析树保留在内存中,因此下次运行不需要重新生成。因此,下一次运行,您将节省解析树的时间。不要低估它,因为这是一个非常昂贵的操作,随着查询复杂度的增加而变得更糟。

有两种方法可以编译查询:使用EntitySQL创建ObjectQuery和使用CompiledQuery.Compile()函数。(请注意,使用页面中的EntityDataSource实际上将使用ObjectQuery和EntitySQL,因此它们将被编译和缓存)。

在这里提一下,如果您不知道什么是EntitySQL。它是一种基于字符串的写EF查询的方法。以下是一个示例:“select value dog from Entities.DogSet as dog where 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();

第一次运行此查询时,框架将生成表达式树并将其保存在内存中。因此,下次执行时,您将节省这个昂贵的步骤。在该示例中,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 );

编译查询的优点在于可以在编译时检查查询语法,而 EntitySQL 则不能。但是,还有其他方面需要考虑......

包含

假设您希望通过查询返回狗主人的数据以避免对数据库进行两次调用。这很容易实现,对吧?

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();
编译查询
    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());

现在,假设你想让Include参数化,也就是说,你想要一个单一的Get()函数,它可以从不同的页面调用,这些页面关心狗的不同关联。有一个关心Owner,另一个关心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();
}

这个 include 仅仅使用传递进来的字符串。非常容易理解。需要注意的是,可以通过创建一个 IncludeMany(string) 函数(接受一个逗号分隔的关联字符串)来改进只接受单一路径的 Include(string) 函数。在扩展部分可以找到此函数。

然而,如果我们试图使用 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()只想在字符串中看到单个路径,而这里我们提供了两个:"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 无法解析 IncludeMany,因为它不是其识别功能的一部分:它是一个扩展。

好的,所以您想将任意数量的路径传递给函数,而 Includes() 只接受一个路径。该怎么办?您可以决定您永远不需要超过20个Includes,并将每个分隔字符串传递到 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...),这会使已缓存的编译查询无效,因为现在它是一个全新的查询!因此,表达式树需要重新解析,您又会遇到性能问题。

那解决办法是什么?您简单地不能使用带参数的Includes和CompiledQueries。改用EntitySQL。这并不意味着没有使用CompiledQueries的场景。对于总是在相同上下文中调用的本地化查询非常有用。理想情况下,应始终使用CompiledQuery,因为该语法在编译时进行检查,但由于某些限制,这是不可能的。

一个使用示例可能是:您可能希望有一个页面来查询哪两只狗有相同的最喜欢食物,这对于BusinessLayer函数来说有点狭窄,因此您将其放在页面中,并确切地知道需要哪些Includes。

传递超过3个参数给CompiledQuery

Func仅限于5个参数,其中最后一个是返回类型,第一个是模型中的Entities对象。因此,您只剩下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();
}

返回类型(这不适用于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(即Linq语句的实际返回类型,它实现了IEnumerable),它将使编译的查询失效并被强制重新解析。因此,经验法则是返回对象的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语句。它可能会慢一点,但我甚至都不确定。可以确定的是,您不必担心误处理ObjectQuery并使编译的查询计划失效。

再次强调,这并不是绝对的说法。ToList()是一种防御性编程技巧,但如果您有正当理由不使用ToList(),那就请随意。在执行查询之前,有许多情况需要先精炼查询。

性能

编译查询的性能影响如何?实际上可能相当大。一个经验法则是,编译并缓存查询以供重用至少需要比不缓存查询执行两倍的时间。对于复杂查询(例如嵌套查询),我见过高达10秒的情况。

因此,第一次调用预编译查询时,会有性能损失。在第一次命中后,性能明显优于相同的非预编译查询。实际上与Linq2Sql几乎相同。

当您首次加载具有预编译查询的页面时,将会命中。它可能需要5-15秒钟的时间(显然会调用多个预编译查询),而随后的加载时间将少于300毫秒。这是一个巨大的差异,您需要决定是否允许第一个用户遭受损失,或者想要编写脚本调用您的页面以强制编译查询。

该查询是否可以缓存?

{
    Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog;
}
不,临时的 Linq 查询不会被缓存,每次调用都需要生成查询树,因此会产生成本。
参数化查询:
大多数搜索功能涉及到高度参数化的查询。甚至有可用的库可以让你通过 lambda 表达式来构建一个参数化查询。问题在于你不能使用预编译的查询。一个解决方法是将查询中所有可能的条件映射出来,并标记您想要使用的条件。
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();
}
这里的优点是您可以获得预编译查询的所有好处。缺点是,您最有可能最终得到一个相当难以维护的where子句,并且会因预编译查询而产生更大的惩罚,每个查询运行的效率也不如可能(特别是在加入联接的情况下)。
另一种方法是逐步构建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个可能的查询(无参数、仅年龄、仅名称和两个参数),但您可以看到,在正常的全球搜索中可能会有更多。
  • 没有人喜欢连接字符串!

另一种选择是查询大量数据的子集,然后在内存中将其缩小。如果您正在处理明确定义的数据子集,例如城市中的所有狗,这尤其有用。您知道有很多狗,但也知道没有那么多......因此,您的城市狗搜索页面可以在内存中加载城市的所有狗,这是一个预编译的查询,然后细化结果。

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 那么最好的解决方案是什么? 没有任何。 您需要选择最适合您和您的问题的解决方案: - 当您不关心预编译查询时,请使用基于lambda的查询构建。 - 当对象结构不太复杂时,请使用完全定义的预编译Linq查询。 - 当结构可能很复杂且可能产生的不同查询数量较少(这意味着较少的预编译命中)时,请使用EntitySQL /字符串连接。 - 当您正在处理较小的数据子集或者首先必须获取所有数据时(如果性能与所有数据一起良好,则在内存中进行筛选不会导致任何时间花费在数据库中)时,请使用内存中的过滤。

单例访问

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

不跟踪,值得吗?

在执行查询时,您可以告诉框架是否跟踪它将返回的对象。这是什么意思?启用跟踪(默认选项)时,框架将跟踪对象正在发生的情况(已修改?已创建?已删除?),并且在从数据库进行进一步查询时链接对象,这是此处感兴趣的内容。

例如,假设ID == 2的狗有一个所有者,其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,因为关联将不受跟踪,并会导致抛出异常。

在页面上,如果绝对没有对数据库的更新,那么可以使用 NoTracking。

混合使用跟踪和 NoTracking 是可行的,但需要您在更新 / 插入 / 删除时格外小心。问题在于,如果混合使用,则有风险使框架试图将 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 是两个不同的对象,一个被追踪,另一个没有。在更新/插入过程中使用未连接对象会强制执行 Attach() 操作并显示 "等一下,我已经有一个具有相同数据库键的对象了。失败"。并且当您 Attach() 一个对象时,它的整个层次结构也会被附加,导致出现各种问题。要特别小心。

使用 NoTracking 会快多少呢?

这取决于查询的情况。有些查询比其他查询更容易受到跟踪的影响。我没有一个快速而简单的规则来解决它,但它确实有帮助。

那么我应该到处使用 NoTracking 吗?

不完全是这样。跟踪对象有一些优点。第一个优点是对象被缓存,因此对于该对象的后续调用将不会访问数据库。该缓存仅在 YourEntities 对象的生命周期内有效,如果您使用上面的单例代码,则与页面生命周期相同。一个页面请求 == 一个 YourEntity 对象。因此,对于同一个对象的多个调用,它将仅在每个页面请求中加载一次。(其他缓存机制可以扩展这个功能)。

当您使用 NoTracking 且尝试多次加载同一对象时会发生什么?每次都会查询数据库,因此会有影响。在单个页面请求期间多少次调用同一个对象?当然尽可能少,但确实会发生。

还要记住上面提到的自动连接关联的部分吗?使用 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属性设置。

在尝试优化性能时需要记住一些事情。

不能使用懒加载,那我该怎么办?

这可以被视为祸中之转。当然,手动加载所有内容很麻烦。但是,它减少了对数据库的调用次数,并迫使您考虑何时应加载数据。您在一个数据库调用中加载的数据越多,就越好。这一直是真的,但现在通过EF的这个“特性”得到了强制执行。

当然,您可以使用 if( !ObjectReference.IsLoaded ) ObjectReference.Load(); 如果您希望,但更好的做法是强制框架在一次加载中加载您知道将需要的对象。这就是关于参数化包含的讨论开始变得有意义的地方。

假设您有一个Dog对象

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

这是你经常使用的函数类型。它会从各个地方调用,并且一旦你拥有了那个Dog对象,你将在不同的函数中对其执行非常不同的操作。首先,它应该被预编译,因为你会经常调用它。其次,每个不同的页面都想要访问Dog数据的不同子集。有些人想要Owner,有些人想要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 " +

15
你难道没有自己的博客或网站可以发布这篇文章吗? - AnthonyWJones
8
@AnthonyWJones,尽管这不是SO上通常的帖子;但是如果您听过SO播客,Jeff总是使用以下口号:SO是一个供没有博客的程序员分享知识的地方。这并不冒犯。基于Jeff的口号,我认为它不应该被关闭。 - BobbyShaftoe
4
@AD,没问题。有时这个社区可能会变得相当荒谬和极权主义。 :) 我想你可以只拿这篇帖子,删除除基本问题以外的所有文本。然后只需发布一个答案。不可思议的是,那可能会获得赞同。我猜我们喜欢官僚主义。 :) 翻译:@AD,没有问题。这个社区有时可能会变得很荒谬和极权主义。 :) 我想你可以将这篇帖子中除了基本问题以外的内容全部删除,然后发布答案。神奇的是,那可能会受到赞同。我猜我们喜欢官僚主义。 :) - BobbyShaftoe
7
我并不特别担心这个问题的长度或复杂程度,只是想说这是31个问题集合成一个问题。以这种方式呈现问题很可能得不到有用的回答,这降低了这篇文章对我们所有人的实用性。你可以重新发布15-20个单独的问题,把它们分开来提问。 - Ben Collins
2
既然它被标记为“不是问题”,我认为这很好。事实上,我真的很喜欢这个“问题”。在SO上给我们一点维基百科的感觉。它强调了SO是高质量信息的资源。当然,在这里找到它比在博客中无休止地搜索要好得多。 - Johannes Rudolph
显示剩余10条评论
2个回答

3

请不要使用以上所有信息,例如“单例访问”。绝对不能将此上下文存储以便重用,因为它不是线程安全的。


+1 这显然违反了工作单元的方法。它不支持取消编辑,并且在保存上下文时,您将很难知道自上次保存以来实际更改了什么。 - surfen

1

虽然这篇文章很有启发性,但我认为分享如何将所有内容融入完整的解决方案架构可能更有帮助。例如,可以展示一个解决方案,其中同时使用EF继承和您的替代方案,以显示它们的性能差异。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接