web-dev-qa-db-ja.com

IQueryableでProjectTo <T>()を呼び出すとAutoMapperがStackOverflowExceptionをスローする

EF Code Firstを使用して、お互いのコレクションを持つクラスを作成しました。エンティティ:

public class Field
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual List<AppUser> Teachers { get; set; }
    public Field()
    {
        Teachers = new List<AppUser>();
    }
}

public class AppUser
{
    public int Id { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string UserName => Email;
    public virtual List<Field> Fields { get; set; }
    public AppUser()
    {
        Fields = new List<FieldDTO>();
    }
}

DTO:

public class FieldDTO
{ 
    public int Id { get; set; }
    public string Name { get; set; }
    public List<AppUserDTO> Teachers { get; set; }
    public FieldDTO()
    {
        Teachers = new List<AppUserDTO>();
    }
}

 public class AppUserDTO
{
    public int Id { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string UserName => Email;
    public List<FieldDTO> Fields { get; set; }
    public AppUserDTO()
    {
        Fields = new List<FieldDTO>();
    }
}

マッピング:

Mapper.CreateMap<Field, FieldDTO>();
Mapper.CreateMap<FieldDTO, Field>();
Mapper.CreateMap<AppUserDTO, AppUser>();
Mapper.CreateMap<AppUser, AppUserDTO>();

そして、私はこのコードを呼び出すときにStackOverflowExceptionを取得しています(コンテキストは私のdbContextです):

protected override IQueryable<FieldDTO> GetQueryable()
{
    IQueryable<Field> query = Context.Fields;
    return query.ProjectTo<FieldDTO>();//exception thrown here
}

これは、Lists内で無限に相互に呼び出してループするために起こると思います。しかし、私はこれがなぜ起こるのか分かりません。私のマッピングは間違っていますか?

19
Peter

自己参照エンティティと自己参照DTOがあります。一般的に、自己参照DTOは悪い考えです。特にプロジェクションを行う場合-EFは、アイテムの階層を結合および結合し、結合する方法を知りません。

2つの選択肢があります。

まず、階層を念頭に置いてDTOを明示的にモデリングすることにより、特定の階層の深さを強制できます。

public class FieldDTO
{ 
    public int Id { get; set; }
    public string Name { get; set; }
    public List<TeacherDTO> Teachers { get; set; }
    public FieldDTO()
    {
        Teachers = new List<TeacherDTO>();
    }
}

public class TeacherDTO 
{
    public int Id { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string UserName => Email;
}

public class AppUserDTO : TeacherDTO
{
    public List<FieldDTO> Fields { get; set; }
    public AppUserDTO()
    {
         Fields = new List<FieldDTO>();
    }
}

これは最も明白で明示的であるため、好ましい方法です。

あまり明確ではなく、あまり明確ではない方法は、AutoMapperが階層関係をトラバースする最大深度を持つように構成することです。

CreateMap<AppUser, AppUserDTO>().MaxDepth(3);

最も簡単に理解できるので、私は#1に行くことを好みますが、#2も同様に機能します。

28
Jimmy Bogard

他のオプションはPreserveReferences()メソッドを使用しています。

CreateMap<AppUser, AppUserDTO>().PreserveReferences();
9
glanes

私はこの一般的な方法を使用します:

        public static TTarget Convert<TSource, TTarget>(TSource sourceItem)
    {
        if (null == sourceItem)
        {
            return default(TTarget);
        }

        var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace, ReferenceLoopHandling = ReferenceLoopHandling.Ignore };

        var serializedObject = JsonConvert.SerializeObject(sourceItem, deserializeSettings);

        return JsonConvert.DeserializeObject<TTarget>(serializedObject);
    }
0
Xtremexploit