development

Entity Framework로 작업 할 때 좋은 디자인 사례는 무엇입니까?

big-blog 2020. 10. 22. 08:20
반응형

Entity Framework로 작업 할 때 좋은 디자인 사례는 무엇입니까?


이는 데이터가 soa를 통해 액세스되지 않는 asp.net 애플리케이션에 주로 적용됩니다. 일부 권장 사항이 여전히 적용되지만 전송 개체가 아닌 프레임 워크에서로드 된 개체에 액세스 할 수 있음을 의미합니다.

이것은 커뮤니티 게시물이므로 적절하다고 생각되면 추가하십시오.

적용 대상 : Visual Studio 2008 sp1과 함께 제공되는 Entity Framework 1.0.

우선 EF를 선택하는 이유는 무엇입니까?

문제가 많은 젊은 기술 (아래 참조)을 고려할 때 프로젝트를 위해 EF 악 대차에 오르는 것은 어려울 수 있습니다. 그러나 이는 Microsoft가 추진하고있는 기술입니다 (EF의 하위 집합 인 Linq2Sql을 희생하여). 또한 NHibernate 또는 다른 솔루션에 만족하지 않을 수 있습니다. 이유가 무엇이든 EF와 함께 일하는 사람들 (나를 포함하여)이 있고 인생은 나쁘지 않습니다.

EF와 상속

첫 번째 큰 주제는 상속입니다. EF는 두 가지 방식으로 지속되는 상속 된 클래스에 대한 매핑을 지원합니다. 즉, 클래스 별 테이블과 계층 구조 테이블입니다. 모델링은 쉽고 해당 부분에 프로그래밍 문제가 없습니다.

(다음은 계층 별 테이블에 대한 경험이 없기 때문에 클래스 별 모델에 적용됩니다. 어쨌든 제한적입니다.) 실제 문제는 일부인 하나 이상의 개체를 포함하는 쿼리를 실행하려고 할 때 발생합니다. 상속 트리 : 생성 된 SQL은 엄청나게 끔찍하고 EF에 의해 구문 분석되는 데 오랜 시간이 걸리며 실행에도 오랜 시간이 걸립니다. 이것은 진짜 쇼 스토퍼입니다. EF는 상속과 함께 사용하지 말아야하거나 가능한 한 적게 사용하면 안됩니다.

다음은 그것이 얼마나 나쁜지에 대한 예입니다. 내 EF 모델에는 ~ 30 개의 클래스가 있으며이 중 ~ 10 개는 상속 트리의 일부였습니다. Base 클래스에서 하나의 항목을 가져 오는 쿼리를 실행할 때 Base.Get (id)와 같이 간단한 것이 생성 된 SQL은 50,000 자 이상이었습니다. 그런 다음 일부 연결을 반환하려고 할 때 한 번에 256 개 이상의 테이블을 쿼리 할 수 ​​없다는 SQL 예외를 throw하는 한 더 많이 퇴화됩니다.

좋아, 이것은 나쁘다. EF 개념은 테이블의 실제 데이터베이스 구현에 대한 고려없이 (또는 가능한 한 적게) 개체 구조를 만들 수 있도록하는 것입니다. 이것은 완전히 실패합니다.

그래서 추천? 가능하다면 상속을 피하면 성능이 훨씬 좋아질 것입니다. 필요한 곳에서는 조금만 사용하십시오. 제 생각에 이것은 EF를 쿼리를위한 훌륭한 SQL 생성 도구로 만들지 만, 여전히 사용하는 것에는 이점이 있습니다. 그리고 상속과 유사한 메커니즘을 구현하는 방법.

인터페이스로 상속 우회

EF와 함께 어떤 종류의 상속을 얻으려고 할 때 가장 먼저 알아야 할 것은 비 EF 모델 클래스에 기본 클래스를 할당 할 수 없다는 것입니다. 시도하지 마십시오. 모델러가 덮어 쓰게됩니다. 그래서 뭘 할건데?

인터페이스를 사용하여 클래스가 일부 기능을 구현하도록 할 수 있습니다. 예를 들어 다음은 디자인 타임에 엔터티 유형이 무엇인지 알지 못하는 EF 엔터티 간의 연결을 정의 할 수있는 IEntity 인터페이스입니다.

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을 저장해야한다는 점을 고려할 때 일반 상속을 갖는 것만 큼 깔끔하지는 않지만 성능 향상을 고려하면 뒤돌아 보지 않을 것입니다.

또한 일대 다, 다 대다 관계를 모델링 할 수 없지만 '유니온'을 창의적으로 사용하면 작동하도록 만들 수 있습니다. 마지막으로 객체의 속성 / 함수에 데이터를로드하는 측면 효과를 생성하므로주의해야합니다. GetXYZ ()와 같은 명확한 명명 규칙을 사용하면 그 점에서 도움이됩니다.

컴파일 된 쿼리

Entity Framework 성능은 ADO (분명히) 또는 Linq2SQL을 사용한 직접적인 데이터베이스 액세스만큼 좋지 않습니다. 그러나이를 개선 할 수있는 방법이 있으며 그중 하나는 쿼리를 컴파일하는 것입니다. 컴파일 된 쿼리의 성능은 Linq2Sql과 유사합니다.

컴파일 된 쿼리 란 무엇입니까? 이는 구문 분석 된 트리를 메모리에 유지하도록 프레임 워크에 지시하는 쿼리 일 뿐이므로 다음에 실행할 때 다시 생성 할 필요가 없습니다. 따라서 다음 실행에서는 트리를 구문 분석하는 데 걸리는 시간을 절약 할 수 있습니다. 더 복잡한 쿼리로 인해 더욱 악화되는 매우 비용이 많이 드는 작업이므로 할인하지 마십시오.

쿼리를 컴파일하는 방법은 EntitySQL로 ObjectQuery를 생성하는 방법과 CompiledQuery.Compile () 함수를 사용하는 두 가지 방법이 있습니다. (페이지에서 EntityDataSource를 사용하면 실제로 EntitySQL과 함께 ObjectQuery를 사용하므로 컴파일 및 캐시됩니다.)

EntitySQL이 무엇인지 모르는 경우를 대비하여 여기에 있습니다. EF에 대한 쿼리를 작성하는 문자열 기반 방법입니다. 다음은 예입니다. "Entities.DogSet에서 dog.ID = @ID 인 dog로 값 선택". 구문은 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 );

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

이제 Include를 매개 변수화하려면 어떻게해야합니까? 내가 의미하는 것은 개에 대한 다른 관계에 관심이있는 다른 페이지에서 호출되는 단일 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();
}

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 개 이상의 Include가 필요하지 않다고 결정하고 구조체에서 분리 된 각 문자열을 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 ...)를 호출 할 때 완전히 새로운 쿼리이므로 캐시 된 컴파일 된 쿼리를 무효화한다는 점을 제외하면 작동해야합니다. 따라서 표현식 트리를 다시 구문 분석해야하며 성능이 다시 저하됩니다.

그렇다면 해결책은 무엇입니까? 매개 변수화 된 Include와 함께 CompiledQueries를 사용할 수 없습니다. 대신 EntitySQL을 사용하십시오. 이것은 CompiledQueries에 대한 용도가 없다는 것을 의미하지는 않습니다. 항상 동일한 컨텍스트에서 호출되는 지역화 된 쿼리에 적합합니다. 이상적으로는 컴파일 타임에 구문이 확인되기 때문에 항상 CompiledQuery를 사용해야하지만 제한으로 인해 불가능합니다.

사용의 예는 다음과 같습니다. 두 마리의 개가 동일한 좋아하는 음식을 가지고있는 페이지를 갖고 싶을 수 있습니다. BusinessLayer 기능에 대해 약간 좁아서 페이지에 넣고 포함 유형이 무엇인지 정확히 알 수 있습니다. 필수입니다.

3 개 이상의 매개 변수를 CompiledQuery에 전달

Func는 5 개의 매개 변수로 제한되며, 마지막 매개 변수는 반환 유형이고 첫 번째 매개 변수는 모델의 엔티티 객체입니다. 따라서 3 개의 매개 변수가 남습니다. pitance이지만 매우 쉽게 개선 할 수 있습니다.

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

반환 유형 (CompiledQuery 메서드와 실행 중에 동시에 컴파일되지 않으므로 EntitySQL 쿼리에는 적용되지 않음)

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 문의 실제 반환 유형)를 계속 사용하면 컴파일 된 쿼리가 무효화되고 강제로 다시 구문 분석됩니다. 따라서 경험의 규칙은 대신 객체의 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 초 안에로드되지만 (분명히 사전 컴파일 된 쿼리가 두 개 이상 호출 될 것임) 후속로드에는 300ms 미만이 소요됩니다. 극적인 차이가 있습니다. 첫 번째 사용자가 히트를 쳐도 괜찮은지 아니면 스크립트가 페이지를 호출하여 쿼리를 강제로 컴파일할지 결정하는 것은 사용자의 몫입니다.

이 쿼리를 캐시 할 수 있습니까?

{
    Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog;
}

아니요, 임시 Linq 쿼리는 캐시되지 않으며 호출 할 때마다 트리를 생성하는 비용이 발생합니다.

매개 변수화 된 쿼리

대부분의 검색 기능에는 크게 매개 변수화 된 쿼리가 포함됩니다. 람바 식에서 매개 변수화 된 쿼리를 작성할 수있는 라이브러리도 있습니다. 문제는 미리 컴파일 된 쿼리를 사용할 수 없다는 것입니다. 한 가지 방법은 쿼리에서 가능한 모든 기준을 매핑하고 사용할 기준에 플래그를 지정하는 것입니다.

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 절로 끝날 가능성이 높고 쿼리를 미리 컴파일하면 더 큰 불이익을 받게되며 실행하는 각 쿼리가 가능한 한 효율적이지 않다는 것입니다 (특히 조인이 던져 짐).

또 다른 방법은 우리 모두가 SQL에서했던 것처럼 EntitySQL 쿼리를 하나씩 빌드하는 것입니다.

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 개뿐입니다 (매개 변수 없음, 연령 전용, 이름 전용 및 두 매개 변수 모두). 일반 세계 검색으로 더 많은 방법이있을 수 있음을 알 수 있습니다. -문자열 연결을 좋아하는 사람은 없습니다!

또 다른 옵션은 데이터의 큰 하위 집합을 쿼리 한 다음 메모리에서 범위를 좁히는 것입니다. 이것은 도시의 모든 개와 같이 데이터의 특정 하위 집합으로 작업하는 경우 특히 유용합니다. 당신은 많은 것을 알고 있지만 그다지 많지 않다는 것을 알고 있습니다 ... 따라서 CityDog 검색 페이지는 단일 미리 컴파일 된 쿼리 인 메모리에 도시의 모든 개를로드 한 다음 결과를 구체화 할 수 있습니다

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을 필터링 할 수 없다는 것을 의미합니다. 그렇다면 최상의 솔루션은 무엇일까요? 아무것도 없습니다. 자신과 문제에 가장 적합한 솔루션을 선택해야합니다.-쿼리를 사전 컴파일하는 데 신경 쓰지 않는 경우 람다 기반 쿼리 빌드를 사용합니다. -개체 구조가 너무 복잡하지 않은 경우 완전히 정의 된 사전 컴파일 된 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();
    }
}

NoTracking, 그만한 가치가 있습니까?

쿼리를 실행할 때 프레임 워크가 반환 할 개체를 추적하도록 지시 할 수 있습니다. 무슨 뜻이에요? 추적이 활성화되면 (기본 옵션) 프레임 워크는 개체에 대해 진행되는 작업 (수정 되었습니까? 생성 되었습니까? 삭제 되었습니까?)을 추적하고 데이터베이스에서 추가 쿼리가 만들어 질 때 개체를 함께 연결합니다. 여기에 관심이 있습니다.

예를 들어, 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;

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

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

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

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 " +

Please do not use all of the above info such as "Singleton access". You absolutely 100% should not be storing this context to be reused as it is not thread safe.


While informative I think it may be more helpful to share how all this fits into a complete solution architecture. Example- Got a solution showing where you use both EF inheritance and your alternative so that it shows their performance difference.

참고URL : https://stackoverflow.com/questions/547278/what-are-good-design-practices-when-working-with-entity-framework

반응형