【问题标题】:Nested query in Entity Framework with AutoMapper使用 AutoMapper 在实体框架中嵌套查询
【发布时间】:2018-03-22 08:37:54
【问题描述】:

用例如下:客户帐户只有一个地址,可以是送货地址,也可以是账单地址。我需要将其合并到一个地址实体中。最重要的是,地址行是数据库中的单独列,但必须是 DTO 中字符串的 IEnumerable。一直以来,这必须支持 ProjectTo(),因为必须实现 OData。如何在不更改任何数据库的情况下完成此操作?由于遗留应用程序,我无法触摸它。

第一个代码示例有效,而第二个代码示例失败:The nested query is not supported. Operation1='Case' Operation2='Collect'。这可能是因为它必须创建子实体。我怎样才能让它工作?是否有任何解决方法(也许重组我的实体)?

在第一个示例中,我将不同地址行列的映射添加到List<string>。然后当调用 .Include() Entity Framework 时,它确切地知道该做什么并且它起作用了。

public void Main()
{
    Mapper.Initialize(cfg =>
    {
        cfg.CreateMap<ShippingAddress, DtoShippingAddress>
            .ForMember(dto => dto.AddressLines,
                entity => entity.MapFrom(source => new List<string>
                    {
                        source.AddressLine1,
                        source.AddressLine2,
                        source.AddressLine3
                    }));

        cfg.CreateMap<BillingAddress, DtoBillingAddress>
            .ForMember(dto => dto.AddressLines,
                entity => entity.MapFrom(source => new List<string>
                    {
                        source.AddressLine1,
                        source.AddressLine2,
                        source.AddressLine3
                    }));
    });
    Mapper.AssertConfigurationIsValid();
    var context = new ClientContext();
    var result = context.CustomerAccounts.Include(i => i.ShippingAddress).Include(i => i.BillingAddress).ProjectTo<DtoCustomerAccount>();//.Dump();
}

public class CustomerAccount
{
    public int Id { get; set; }

    public ShippingAddress ShippingAddress { get; set; }

    public BillingAddress BillingAddress { get; set; }
}

public class ShippingAddress
{
    public int Id { get; set; }

    public string AddressLine1 { get; set; }

    public string AddressLine2 { get; set; }

    public string AddressLine3 { get; set; }
}

public class BillingAddress
{
    public int Id { get; set; }

    public string AddressLine1 { get; set; }

    public string AddressLine2 { get; set; }

    public string AddressLine3 { get; set; }
}

public class DtoCustomerAccount
{
    public int Id { get; set; }

    public DtoShippingAddress ShippingAddress { get; set; }

    public DtoBillingAddress BillingAddress { get; set; }
}

public class DtoShippingAddress
{
    public int Id { get; set; }

    public List<string> AddressLines { get; set; }
}

public class DtoBillingAddress
{
    public int Id { get; set; }

    public List<string> AddressLines { get; set; }
}

public class Initializer : DropCreateDatabaseAlways<ClientContext>
{       
    protected override void Seed(ClientContext context)
    {
        context.CustomerAccounts.Add(new UserQuery.CustomerAccount {ECommercePublished=true,  Articles = new []{ new Article{IsDefault=true, NationId=1, ProductId=1}}});
    }
}

public class ClientContext : DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    Database.Log = s=>Debug.WriteLine(s);
        Database.SetInitializer(new Initializer());
    }

    public DbSet<CustomerAccount> CustomerAccounts { get; set; }
}

我需要的实现方式,失败了:

//The nested query is not supported. Operation1='Case' Operation2='Collect'
public void Main()
{
    Mapper.Initialize(cfg =>
    {
        cfg.CreateMap<CustomerAccount, DtoCustomerAccount>
            .ForMember(dto => dto.Address,
                entity => entity.MapFrom(source =>
                    source.BillingAddress == null
                        ? new Address
                        {
                            Id = source.ShippingAddress.Id,
                            AddressLines = new [] // or new List<string>
                            {
                                source.ShippingAddress.AddressLine1,
                                source.ShippingAddress.AddressLine2,
                                source.ShippingAddress.AddressLine3
                            }
                        }
                        : new Address
                        {
                            Id = source.BillingAddress.Id,
                            AddressLines = new [] // or new List<string>
                            {
                                source.BillingAddress.AddressLine1,
                                source.BillingAddress.AddressLine2,
                                source.BillingAddress.AddressLine3
                            }
                        }));
    });
    Mapper.AssertConfigurationIsValid();
    var context = new ClientContext();
    var result = context.CustomerAccounts.ProjectTo<DtoCustomerAccount>();//.Dump();
}

public class CustomerAccount
{
    public int Id { get; set; }

    public ShippingAddress ShippingAddress { get; set; }

    public BillingAddress BillingAddress { get; set; }
}

public class ShippingAddress
{
    public int Id { get; set; }

    public string AddressLine1 { get; set; }

    public string AddressLine2 { get; set; }

    public string AddressLine3 { get; set; }
}

public class BillingAddress
{
    public int Id { get; set; }

    public string AddressLine1 { get; set; }

    public string AddressLine2 { get; set; }

    public string AddressLine3 { get; set; }
}

public class DtoCustomerAccount
{
    public int Id { get; set; }

    public DtoAddress Address { get; set; }
}

public class DtoAddress
{
    public int Id { get; set; }

    public List<string> AddressLines { get; set; }
}

public class Initializer : DropCreateDatabaseAlways<ClientContext>
{       
    protected override void Seed(ClientContext context)
    {
        context.CustomerAccounts.Add(new UserQuery.CustomerAccount {ECommercePublished=true,  Articles = new []{ new Article{IsDefault=true, NationId=1, ProductId=1}}});
    }
}

public class ClientContext : DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    Database.Log = s=>Debug.WriteLine(s);
        Database.SetInitializer(new Initializer());
    }

    public DbSet<CustomerAccount> CustomerAccounts { get; set; }
}

【问题讨论】:

    标签: c# entity-framework automapper


    【解决方案1】:

    从技术上讲,这不是 AutoMapper 问题,因为 ProjectTo 只是使用您提供的表达式。在这种情况下,List&lt;string&gt; 的 EF 查询技巧不适用于条件运算符,因为 BillingAddress 和 ShippingAddress 是导航属性,并且 EF 翻译器试图使用无法与条件运算符组合的子查询。因此,您需要找到与 EF 查询兼容的方式来指定所需的投影。

    一种可能的方法是对每个结果地址属性使用条件运算符:

    .ForMember(dto => dto.Address, entity => entity.MapFrom(source => new DtoAddress
    {
        Id = source.BillingAddress != null ? source.BillingAddress.Id : source.ShippingAddress.Id,
        AddressLines = new List<string>
        {
            source.BillingAddress != null ? source.BillingAddress.AddressLine1 : source.ShippingAddress.AddressLine1,
            source.BillingAddress != null ? source.BillingAddress.AddressLine2 : source.ShippingAddress.AddressLine2,
            source.BillingAddress != null ? source.BillingAddress.AddressLine3 : source.ShippingAddress.AddressLine3,
        }   
    }))
    

    【讨论】:

    • 我尝试了你建议的方法,它可以工作一层。但是当我添加下一个级别时,我再次收到相同的错误消息。我已经创建了一个要点,你可以看看吗? gist.github.com/bdebaere/…
    • 困难重重的伙伴。在进行这些数组技巧之前,需要找到一种方法来使整个事物变平。很可能是一些中间投影或其他东西。有时间我会玩它并告诉你。
    • 好的,这是诀窍 - 使用平面数据创建数组,然后使用 Select 生成行。 pastebin.com/ytZdML5u
    • 谢谢,今天我了解了这个解决方法。它不仅对我有帮助,在其他领域也对我有帮助。
    猜你喜欢
    • 1970-01-01
    • 2014-05-19
    • 1970-01-01
    • 2010-12-06
    • 1970-01-01
    • 2011-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多