programing

Entity Framework로 작업할 때 좋은 설계 방법은 무엇입니까?

abcjava 2023. 7. 10. 21:57
반응형

Entity Framework로 작업할 때 좋은 설계 방법은 무엇입니까?

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

이 게시물은 커뮤니티 게시물이므로 적합하다고 판단되는 대로 추가해 주시기 바랍니다.

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

왜 애초에 EF를 선택해야 합니까?

문제가 많은 젊은 기술임을 고려하면(아래 참조), 프로젝트의 EF 시류에 편승하는 것은 어려울 수 있습니다.하지만 Microsoft가 (EF의 하위 집합인 Linq2Sql을 희생하여) 추진하고 있는 기술입니다.또한 NHN이나 다른 솔루션에 만족하지 못할 수도 있습니다.이유가 무엇이든 간에 (저를 포함하여) EF와 함께 일하는 사람들이 있고 삶이 나쁘지 않다고 생각하게 합니다.

EF 및 상속

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

(다음은 계층별 테이블에 대한 경험이 없기 때문에 클래스 모델별 테이블에 적용됩니다.)실제 문제는 상속 트리의 일부인 하나 이상의 개체를 포함하는 쿼리를 실행하려고 할 때 발생합니다. 생성된 sql은 매우 심각하고 EF에 의해 구문 분석되는 데 시간이 오래 걸립니다.이것은 정말 쇼 스토퍼입니다.EF를 상속과 함께 사용하거나 가능한 한 적게 사용하면 안 될 정도로 충분합니다.

여기 그것이 얼마나 나빴는지에 대한 예가 있습니다.제 EF 모델에는 ~30개의 클래스가 있었고, 그 중 ~10개는 상속 트리의 일부였습니다.기본 클래스에서 하나의 항목을 가져오는 쿼리를 실행할 때 기본과 같은 간단한 것입니다.Get(id). 생성된 SQL이 50,000자를 초과했습니다.그런 다음 일부 연결을 반환하려고 하면 더 많이 저하되어 한 번에 256개 이상의 테이블을 쿼리할 수 없는 SQL 예외가 발생합니다.

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

그래서, 추천?가능하면 상속을 피하세요, 성능이 훨씬 더 좋아질 것입니다.당신이 필요한 곳에 그것을 적게 사용하세요.제 생각에, 이것은 EF를 쿼리를 위한 미화된 SQL 생성 도구로 만들었지만, 그것을 사용하는 것에는 여전히 이점이 있습니다.그리고 상속과 유사한 메커니즘을 구현하는 방법.

인터페이스를 사용하여 상속 무시

EF를 사용하여 일종의 상속을 수행하려고 할 때 가장 먼저 알아야 할 것은 EF 모델이 아닌 클래스를 기본 클래스로 할당할 수 없다는 것입니다.시도조차 하지 마세요, 모델러가 덮어쓸 거예요.그래서 어떻게 해야 하나요?

인터페이스를 사용하여 클래스가 일부 기능을 구현하도록 할 수 있습니다.예를 들어, 설계 시점에 엔티티 유형을 알 수 없는 EF 엔티티 간의 연결을 정의할 수 있는 IE 엔티티 인터페이스가 있습니다.

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

이 엔티티를 사용하여 다른 클래스의 정의되지 않은 연결로 작업할 수 있습니다.

// 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()와 같은 명확한 명명 규칙을 사용하는 것이 도움이 됩니다.

컴파일된 쿼리

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

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

쿼리를 컴파일하는 두 가지 방법이 있습니다. 엔티티를 사용하여 개체 쿼리 만들기SQL 및 컴파일된 쿼리를 사용합니다.컴파일() 함수입니다.(페이지에서 EntityDataSource를 사용하면 실제로 ObjectQuery를 Entity와 함께 사용하게 됩니다.SQL(즉, 컴파일되고 캐시됨 및 캐시됨).

EntitySQL이 무엇인지 모를 경우를 대비하여 여기를 제외합니다.EF에 대한 쿼리를 작성하는 문자열 기반 방식입니다.다음은 예입니다. "엔티티에서 값 개 선택".개는 개가 있는 곳에서 개로 설정됩니다.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는 기본 옵션이므로 필요하지 않습니다.

나중에 사용하기 위해 쿼리를 컴파일하는 다른 방법은 컴파일된 쿼리입니다.컴파일 방법.대리자를 사용합니다.

    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의 장점은 컴파일 시 쿼리의 구문을 확인할 수 있다는 것입니다.SQL은 그렇지 않습니다.하지만, 다른 고려 사항이 있습니다...

포함한다

데이터베이스에 두 번 호출하지 않도록 쿼리에서 개 소유자에 대한 데이터를 반환하려고 합니다.하기 쉽죠?

엔티티 SQl

        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() 함수를 원한다는 것입니다.하나는 주인에게, 다른 하나는 좋아하는 음식에, 다른 하나는 그의 좋아하는 타이어에 관심이 있습니다.장난감 등등.기본적으로 쿼리에 로드할 연결을 지정하려고 합니다.

엔티티를 사용하여 쉽게 수행할 수 있습니다.SQL

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

포함은 단순히 전달된 문자열을 사용합니다.충분히 쉽습니다.로드할 쉼표로 구분된 연결 문자열을 전달할 수 있는 IncludeMany(문자열)를 사용하면 Include(문자열)(단일 경로만 허용) 기능을 개선할 수 있습니다.이 기능에 대한 자세한 내용은 확장 섹션을 참조하십시오.

그러나 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"와 "Favorite Food"("Owner"와 혼동해서는 안 됨)를 2개 제공합니다.좋아하는 음식"!).

그러면 확장 함수인 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개를 포함하고 구조체에서 분리된 각 문자열을 CompilledQuery에 전달하는 것보다 더 많이 필요하지 않다고 결정할 수 있습니다.그러나 이제 쿼리는 다음과 같습니다.

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

그것 또한 끔찍합니다.좋아요, 그럼, 하지만 잠시만요.컴파일된 쿼리로 개체 쿼리 <>를 반환할 수 없습니까?그럼 거기에 포함을 설정해주세요.음, 저도 그렇게 생각했을 겁니다.

    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...)를 호출할 때 캐시된 컴파일된 쿼리는 이제 완전히 새로운 쿼리이므로 무효화한다는 점을 제외하고는 효과가 있었습니다!따라서 표현식 트리를 다시 분석해야 합니다. 그러면 성능이 다시 향상됩니다.

그렇다면 해결책은 무엇일까요?매개 변수화된 포함과 함께 컴파일된 쿼리를 사용할 수 없습니다.엔티티 사용대신 SQL.이것은 컴파일된 쿼리에 대한 사용이 없다는 것을 의미하지 않습니다.항상 동일한 컨텍스트에서 호출되는 현지화된 쿼리에 적합합니다.이상적인 컴파일 쿼리는 컴파일 시 구문을 확인하므로 항상 사용해야 하지만 제한 때문에 사용할 수 없습니다.

예를 들어, 어떤 두 마리의 개가 좋아하는 음식을 가지고 있는지 쿼리하는 페이지가 필요할 수 있습니다. 이 페이지는 BusinessLayer 기능에 비해 약간 좁기 때문에 페이지에 넣고 어떤 종류의 음식이 필요한지 정확히 알 수 있습니다.

3개 이상의 매개 변수를 컴파일된 쿼리로 전달

Func는 5개의 매개 변수로 제한되며, 그 중 마지막 매개 변수는 반환 유형이고 첫 번째 매개 변수는 모델의 엔티티 개체입니다.그러면 세 가지 매개 변수가 남게 됩니다.불쌍하지만, 매우 쉽게 개선될 수 있습니다.

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 메서드와 실행 중 동시에 컴파일되지 않기 때문에 SQL 쿼리)

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(IEnumberable을 구현하는 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 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;
}

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

매개 변수화된 쿼리

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

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에 대해 모두가 했던 것처럼 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;
}

특히 모든 데이터를 표시하고 필터링을 허용할 때 유용합니다.

문제: - 하위 집합에 주의하지 않으면 심각한 데이터 전송이 발생할 수 있습니다. - 반환한 데이터에 대해서만 필터링할 수 있습니다.개를 돌려주지 않으면 그렇다는 뜻입니다.소유자 연결, 개를 필터링할 수 없습니다.Owner.Name 그럼 가장 좋은 해결책은 무엇입니까?없습니다.당신은 당신과 당신의 문제에 가장 적합한 솔루션을 선택해야 합니다: - 당신의 쿼리를 미리 컴파일하는 것에 관심이 없을 때 람다 기반 쿼리 빌딩을 사용하세요. - 당신의 객체 구조가 너무 복잡하지 않을 때 완전하게 정의된 사전 컴파일된 Linq 쿼리를 사용하세요. - 엔티티 사용구조가 복잡할 수 있고 가능한 다른 결과 쿼리 수가 적을 때(즉, 컴파일 전 히트 수가 적을 때) SQL/string 연결.데이터의 작은 부분 집합으로 작업할 때 또는 처음에 데이터의 모든 데이터를 가져와야 할 때 메모리 내 필터링을 사용합니다(모든 데이터의 성능이 양호한 경우 메모리 내 필터링으로 인해 DB에서 시간이 낭비되지 않습니다)

싱글턴 액세스

모든 페이지에서 컨텍스트와 엔티티를 처리하는 가장 좋은 방법은 싱글톤 패턴을 사용하는 것입니다.

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;

추적은 매우 유용하며 성능 문제가 없는 완벽한 환경에서는 항상 켜져 있을 것입니다.하지만 이 세상에는 성능 면에서 대가가 있습니다.그렇다면, 여러분은 속도를 높이기 위해 추적 금지를 사용해야 할까요?데이터를 사용하려는 용도에 따라 다릅니다.

No Tracking으로 쿼리한 데이터를 데이터베이스에서 업데이트/삽입/삭제하는 데 사용할 수 있는 가능성이 있습니까?이 경우 연결이 추적되지 않으므로 예외가 발생하므로 추적 안 함을 사용하지 마십시오.

데이터베이스에 대한 업데이트가 전혀 없는 페이지에서 추적 없음을 사용할 수 있습니다.

추적 및 추적 없음을 혼합할 수 있지만 업데이트/삽입/삭제에 각별히 주의해야 합니다.문제는 혼합할 경우 프레임워크가 추적이 적용된 동일한 개체의 다른 복사본이 존재하는 컨텍스트에 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는 서로 다른 두 개체입니다. 하나는 추적된 개체이고 하나는 추적되지 않은 개체입니다.업데이트/삽입에서 분리된 개체를 사용하면 "잠깐만, 동일한 데이터베이스 키를 가진 개체가 이미 여기에 있습니다.실패." 그리고 한 개체를 연결()하면 모든 개체의 계층 구조가 연결되어 모든 곳에서 문제가 발생합니다.특히 조심하세요.

추적 기능이 없는 경우 얼마나 더 빠릅니까?

쿼리에 따라 다릅니다.어떤 것들은 다른 것들보다 훨씬 더 추적하기 쉽습니다.저는 그것을 위한 빠른 규칙을 가지고 있지 않지만, 도움이 됩니다.

그럼 어디서나 추적 금지를 사용해야 합니까?

정확히는 아닙니다.개체를 추적하면 몇 가지 이점이 있습니다.첫 번째는 개체가 캐시되므로 해당 개체에 대한 후속 호출이 데이터베이스에 도달하지 않는다는 것입니다.이 캐시는 위의 싱글턴 코드를 사용하는 경우 페이지 수명과 동일한 YourEntities 개체의 수명 동안만 유효합니다.한 페이지 요청 == 하나의 YourEntity 개체입니다.따라서 동일한 개체에 대한 여러 호출의 경우 페이지 요청당 한 번만 로드됩니다. (다른 캐싱 메커니즘은 이를 확장할 수 있습니다.)

추적 없음을 사용하고 동일한 개체를 여러 번 로드하려고 하면 어떻게 됩니까?데이터베이스는 매번 쿼리되므로 영향이 있습니다.단일 페이지 요청 중에 동일한 개체를 얼마나 자주 호출해야 합니까?물론 가능한 한 적게, 하지만 실제로 일어납니다.

또한 연결을 자동으로 연결하는 방법에 대한 위의 내용을 기억하십니까?추적 기능이 없는 경우에는 이러한 기능이 없으므로 여러 배치로 데이터를 로드하는 경우 다음과 같은 링크가 제공되지 않습니다.

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

이 경우, 어떤 개도 자신의 개를 가질 수 없습니다.소유자 속성 집합입니다.

성능을 최적화할 때 주의해야 할 몇 가지 사항입니다.

게으른 짐은 없는데, 어떻게 해야 하나요?

이것은 전화위복으로 볼 수 있습니다.물론 모든 것을 수동으로 로드하는 것은 귀찮습니다.그러나 DB에 대한 호출 수를 줄이고 데이터를 로드해야 하는 시기를 생각해야 합니다.하나의 데이터베이스에 더 많이 로드할수록 더 많은 것을 호출합니다.그것은 항상 사실이었지만, 지금은 EF의 이 '기능'으로 시행되고 있습니다.

물론 if(!ObjectReference)를 호출할 수 있습니다.IsLoaded ) 개체 참조입니다.로드(); 원할 경우, 그러나 더 나은 방법은 프레임워크가 필요하다는 것을 알고 있는 객체를 한 번에 로드하도록 강제하는 것입니다.여기서 매개변수화된 포함에 대한 논의가 이해되기 시작합니다.

당신이 개를 가지고 있다고 치자.

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

이 기능은 항상 사용하는 기능입니다.그것은 모든 곳에서 호출되고, 일단 당신이 Dog 객체를 갖게 되면, 당신은 다른 기능들로 그것에 매우 다른 일들을 하게 될 것입니다.먼저, 당신은 그것을 매우 자주 부를 것이기 때문에, 그것은 미리 컴파일되어야 합니다.둘째, 각 페이지는 Dog 데이터의 다른 하위 집합에 액세스할 수 있습니다.어떤 이는 주인을 원하고 어떤 이는 가장 좋아하는 사람을 원할 것입니다.장난감 등

물론 필요할 때마다 각 참조에 대해 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% 저장해서는 안 됩니다.

이 모든 것이 완벽한 솔루션 아키텍처에 어떻게 적합한지를 공유하는 것이 유용할 수도 있습니다.예제 - EF 상속과 대체 방법을 모두 사용하여 성능 차이를 표시하는 솔루션을 얻었습니다.

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

반응형