无法将可查询方法转换为可枚举方法。这可能是 Entity Framework 中的一个问题

Unable to convert a queryable method to an enumerable method. This is likely an issue in Entity Framework

我正在创建一个有点复杂的查询来检索与实体的多对多关系。这是创建查询的代码:

  protected virtual async Task<IQueryable<ContactWithNavigationProperties>> GetQueryForNavigationPropertiesAsync()
  {
    var dbContext = await GetDbContextAsync();
    return from contact in await GetDbSetAsync()
           join department in dbContext.Departments
             on contact.DepartmentId equals department.Id into departments
           from department in departments.DefaultIfEmpty()
           join client in dbContext.Clients
             on contact.ClientId equals client.Id into clients
           from client in clients.DefaultIfEmpty()
           join contactAddress in dbContext.Set<ContactAddress>()
             on contact.Id equals contactAddress.ContactId into contactAddresses
           from contactAddress in contactAddresses.DefaultIfEmpty()
           join contactEmailAddress in dbContext.Set<ContactEmailAddress>()
             on contact.Id equals contactEmailAddress.ContactId into contactEmailAddresses
           from contactEmailAddress in contactEmailAddresses.DefaultIfEmpty()
           join contactPhoneNumber in dbContext.Set<ContactPhoneNumber>()
             on contact.Id equals contactPhoneNumber.ContactId into contactPhoneNumbers
           from contactPhoneNumber in contactPhoneNumbers.DefaultIfEmpty()
           select new ContactWithNavigationProperties
           {
             Contact = contact,
             Department = department,
             Client = client,
             Addresses = (from ca in contactAddresses
                          join address in dbContext.Addresses
                            on ca.AddressId equals address.Id into addresses
                          from address in addresses.DefaultIfEmpty()
                          select address).ToList(),
             EmailAddresses = (from cea in contactEmailAddresses
                               join emailAddress in dbContext.EmailAddresses
                                 on cea.EmailAddressId equals emailAddress.Id into emailAddresses
                               from emailAddress in emailAddresses.DefaultIfEmpty()
                               select emailAddress).ToList(),
             PhoneNumbers = (from cpn in contactPhoneNumbers
                             join phoneNumber in dbContext.PhoneNumbers
                               on cpn.PhoneNumberId equals phoneNumber.Id into phoneNumbers
                             from phoneNumber in phoneNumbers.DefaultIfEmpty()
                             select phoneNumber).ToList()
           };
  }

执行此查询后,我收到以下异常:

2021-12-06 07:49:17.867 -06:00 [ERR] Unable to convert a queryable method to an enumerable method. This is likely an issue in Entity Framework, please file an issue at https://go.microsoft.com/fwlink/?linkid=2142044.
System.InvalidOperationException: Unable to convert a queryable method to an enumerable method. This is likely an issue in Entity Framework, please file an issue at https://go.microsoft.com/fwlink/?linkid=2142044.
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ConvertToEnumerable(MethodInfo queryableMethod, IEnumerable`1 arguments)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.PendingSelectorExpandingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.Expand(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPreprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at MyProject.Contacts.EfCoreContactRepository.GetListWithNavigationPropertiesAsync(String filterText, String name, String surname, String title, Nullable`1 lastContactDateMin, Nullable`1 lastContactDateMax, String note, Nullable`1 departmentId, Nullable`1 clientId, Nullable`1 primary, Nullable`1 active, String sorting, Int32 maxResultCount, Int32 skipCount, CancellationToken cancellationToken) in D:\Century\Internal\Clients.Link\Clients.Link\src\MyProject.EntityFrameworkCore\Contacts\EfCoreContactRepository.cs:line 54
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at MyProject.Contacts.ContactsAppService.GetListAsync(GetContactsInput input) in D:\Century\Internal\Clients.Link\Clients.Link\src\MyProject.Application\Contacts\ContactAppService.cs:line 59
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Authorization.AuthorizationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.GlobalFeatures.GlobalFeatureInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Auditing.AuditingInterceptor.ProceedByLoggingAsync(IAbpMethodInvocation invocation, IAuditingHelper auditingHelper, IAuditLogScope auditLogScope)
   at Volo.Abp.Auditing.AuditingInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at lambda_method3642(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

所以在我提交错误之前,我希望 Entity Framework 方面的专家可以看看我的查询并指出任何明显的问题。

这是我的 csproj 文件及其依赖项:

<Project Sdk="Microsoft.NET.Sdk">

  <Import Project="..\..\common.props" />

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <RootNamespace>MyProject</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <Compile Remove="Addresses\AddressesDataSeedContributorWithDependencies.cs" />
    <Compile Remove="Campaigns\CampaignsDataSeedContributor.cs" />
    <Compile Remove="ClientNeeds\ClientNeedsDataSeedContributor.cs" />
    <Compile Remove="Clients\ClientsDataSeedContributorWithDependencies.cs" />
    <Compile Remove="Contacts\ContactsDataSeedContributorWithDependencies.cs" />
    <Compile Remove="Countries\CountriesDataSeedContributor.cs" />
    <Compile Remove="Departments\DepartmentsDataSeedContributor.cs" />
    <Compile Remove="EmailAddresses\EmailAddressesDataSeedContributor.cs" />
    <Compile Remove="EntityFrameworkCore\MyProjectDataSeeder.cs" />
    <Compile Remove="LeadSources\LeadSourcesDataSeedContributor.cs" />
    <Compile Remove="Migrations\MyProjectDbContextModelSnapshot - Copy.cs" />
    <Compile Remove="Opportunities\OpportunitiesDataSeedContributorWithDependencies.cs" />
    <Compile Remove="Opportunities\OpportunityLineItemsDataSeedContributorWithDependencies.cs" />
    <Compile Remove="PhoneNumbers\PhoneNumbersDataSeedContributor.cs" />
    <Compile Remove="ProductGroups\ProductGroupsDataSeedContributor.cs" />
    <Compile Remove="Products\ProductsDataSeedContributorWithDependencies.cs" />
    <Compile Remove="RevenueRepeatFreqs\RevenueRepeatFreqsDataSeedContributor.cs" />
    <Compile Remove="States\StatesDataSeedContributorWithDependencies.cs" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyProject.Domain\MyProject.Domain.csproj" />
    <PackageReference Include="Microsoft.Data.SqlClient" Version="3.0.1" />
    <PackageReference Include="Volo.Abp.EntityFrameworkCore.SqlServer" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.SettingManagement.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.IdentityServer.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.BackgroundJobs.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.AuditLogging.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.BlobStoring.Database.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.Identity.Pro.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.LanguageManagement.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Saas.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.Abp.TextTemplateManagement.EntityFrameworkCore" Version="4.4.4" />
    <PackageReference Include="Volo.CmsKit.Pro.EntityFrameworkCore" Version="4.4.4" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.*">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
      <PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets>
    </PackageReference>
  </ItemGroup>

</Project>

如您所见,我正在使用带有 EF 5.0 的 ABP Commercial 4.4.4。感谢您的帮助。

编辑

要求我包括我的数据库上下文和适用的实体定义。这里是:

MyProductDbContext

[ConnectionStringName("Default")]
public class MyProductDbContext : MyProductDbContextBase<MyProductDbContext>
{
  public MyProductDbContext(DbContextOptions<MyProductDbContext> options)
    : base(options) { }

  public DbSet<Contact> Contacts { get; set; }
  public DbSet<EmailAddress> EmailAddresses { get; set; }
  public DbSet<PhoneNumber> PhoneNumbers { get; set; }
  public DbSet<Department> Departments { get; set; }
  public DbSet<Address> Addresses { get; set; }
  public DbSet<State> States { get; set; }
  public DbSet<Country> Countries { get; set; }

  protected override void OnModelCreating(ModelBuilder builder)
  {
    builder.SetMultiTenancySide(MultiTenancySides.Both);

    base.OnModelCreating(builder);
    if (builder.IsHostDatabase())
    {
      builder.Entity<Country>(b =>
      {
        b.ToTable(MyProductConsts.DbTablePrefix + "Countries", MyProductConsts.DbSchema);
        b.ConfigureByConvention();
        b.Property(x => x.Code).HasColumnName(nameof(Country.Code)).IsRequired()
         .HasMaxLength(CountryConsts.CodeMaxLength);
        b.Property(x => x.Name).HasColumnName(nameof(Country.Name)).IsRequired()
         .HasMaxLength(CountryConsts.NameMaxLength);
        b.Property(x => x.Primary).HasColumnName(nameof(Country.Primary));
        b.Property(x => x.Active).HasColumnName(nameof(Country.Active));
      });
    }

    builder.Entity<State>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "States", MyProductConsts.DbSchema);
      b.ConfigureByConvention();
      b.Property(x => x.TenantId).HasColumnName(nameof(State.TenantId));
      b.Property(x => x.Code).HasColumnName(nameof(State.Code)).IsRequired().HasMaxLength(StateConsts.CodeMaxLength);
      b.Property(x => x.Name).HasColumnName(nameof(State.Name)).IsRequired().HasMaxLength(StateConsts.NameMaxLength);
      b.Property(x => x.Primary).HasColumnName(nameof(State.Primary));
      b.Property(x => x.Active).HasColumnName(nameof(State.Active));

      // TODO - Implement referential integrity checks in a StateManager domain service
      //b.HasOne<Country>().WithMany().IsRequired().HasForeignKey(x => x.CountryId);
    });
    builder.Entity<Address>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "Addresses", MyProductConsts.DbSchema);
      b.ConfigureByConvention();
      b.Property(x => x.TenantId).HasColumnName(nameof(Address.TenantId));
      b.Property(x => x.Name).HasColumnName(nameof(Address.Name)).IsRequired()
       .HasMaxLength(AddressConsts.NameMaxLength);
      b.Property(x => x.AddressType).HasColumnName(nameof(Address.AddressType)).IsRequired();
      b.Property(x => x.Line1).HasColumnName(nameof(Address.Line1)).IsRequired()
       .HasMaxLength(AddressConsts.Line1MaxLength);
      b.Property(x => x.Line2).HasColumnName(nameof(Address.Line2)).HasMaxLength(AddressConsts.Line2MaxLength);
      b.Property(x => x.Line3).HasColumnName(nameof(Address.Line3)).HasMaxLength(AddressConsts.Line3MaxLength);
      b.Property(x => x.City).HasColumnName(nameof(Address.City)).IsRequired()
       .HasMaxLength(AddressConsts.CityMaxLength);
      b.Property(x => x.PostalCode).HasColumnName(nameof(Address.PostalCode)).IsRequired()
       .HasMaxLength(AddressConsts.PostalCodeMaxLength);
      b.Property(x => x.Primary).HasColumnName(nameof(Address.Primary));
      b.Property(x => x.Active).HasColumnName(nameof(Address.Active));

      // TODO - Implement referential integrity checks in an AddressManager domain service
      //b.HasOne<State>().WithMany().IsRequired().HasForeignKey(x => x.StateId);
      //b.HasOne<Country>().WithMany().IsRequired().HasForeignKey(x => x.CountryId);
    });
    builder.Entity<Department>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "Departments", MyProductConsts.DbSchema);
      b.ConfigureByConvention();
      b.Property(x => x.TenantId).HasColumnName(nameof(Department.TenantId));
      b.Property(x => x.Code).HasColumnName(nameof(Department.Code)).HasMaxLength(DepartmentConsts.CodeMaxLength);
      b.Property(x => x.Name).HasColumnName(nameof(Department.Name)).IsRequired()
       .HasMaxLength(DepartmentConsts.NameMaxLength);
      b.Property(x => x.Primary).HasColumnName(nameof(Department.Primary));
      b.Property(x => x.Active).HasColumnName(nameof(Department.Active));
    });
    builder.Entity<EmailAddress>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "EmailAddresses", MyProductConsts.DbSchema);
      b.ConfigureByConvention();
      b.Property(x => x.TenantId).HasColumnName(nameof(EmailAddress.TenantId));
      b.Property(x => x.Name).HasColumnName(nameof(EmailAddress.Name)).IsRequired()
       .HasMaxLength(EmailAddressConsts.NameMaxLength);
      b.Property(x => x.EmailAddressType).HasColumnName(nameof(EmailAddress.EmailAddressType)).IsRequired();
      b.Property(x => x.Email).HasColumnName(nameof(EmailAddress.Email)).IsRequired()
       .HasMaxLength(EmailAddressConsts.EmailMaxLength);
      b.Property(x => x.Primary).HasColumnName(nameof(EmailAddress.Primary));
      b.Property(x => x.Active).HasColumnName(nameof(EmailAddress.Active));
    });
    builder.Entity<PhoneNumber>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "PhoneNumbers", MyProductConsts.DbSchema);
      b.ConfigureByConvention();
      b.Property(x => x.TenantId).HasColumnName(nameof(PhoneNumber.TenantId));
      b.Property(x => x.Name).HasColumnName(nameof(PhoneNumber.Name)).IsRequired()
       .HasMaxLength(PhoneNumberConsts.NameMaxLength);
      b.Property(x => x.PhoneNumberType).HasColumnName(nameof(PhoneNumber.PhoneNumberType)).IsRequired();
      b.Property(x => x.Number).HasColumnName(nameof(PhoneNumber.Number)).IsRequired()
       .HasMaxLength(PhoneNumberConsts.NumberMaxLength);
      b.Property(x => x.Primary).HasColumnName(nameof(PhoneNumber.Primary));
      b.Property(x => x.Active).HasColumnName(nameof(PhoneNumber.Active));
      b.Property(x => x.Extension).HasColumnName(nameof(PhoneNumber.Extension))
       .HasMaxLength(PhoneNumberConsts.ExtensionMaxLength);
    });
    builder.Entity<Contact>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "Contacts", MyProductConsts.DbSchema);
      b.ConfigureByConvention();
      b.Property(x => x.TenantId).HasColumnName(nameof(Contact.TenantId));
      b.Property(x => x.Name).HasColumnName(nameof(Contact.Name)).HasMaxLength(ContactConsts.NameMaxLength);
      b.Property(x => x.Surname).HasColumnName(nameof(Contact.Surname)).IsRequired()
       .HasMaxLength(ContactConsts.SurnameMaxLength);
      b.Property(x => x.Title).HasColumnName(nameof(Contact.Title)).HasMaxLength(ContactConsts.TitleMaxLength);
      b.Property(x => x.LastContactDate).HasColumnName(nameof(Contact.LastContactDate))
       .HasMaxLength(ContactConsts.TitleMaxLength);
      b.Property(x => x.Note).HasColumnName(nameof(Contact.Note));
      b.Property(x => x.Primary).HasColumnName(nameof(Contact.Primary));
      b.Property(x => x.Active).HasColumnName(nameof(Contact.Active));

      // many-to-one relationships
      b.HasOne<Department>().WithMany().HasForeignKey(x => x.DepartmentId);
      b.HasOne<Client>().WithMany().HasForeignKey(x => x.ClientId).IsRequired(false);

      // many-to-many relationships
      b.HasMany(x => x.ContactAddresses).WithOne().HasForeignKey(x => x.ContactId).IsRequired();
      b.HasMany(x => x.ContactEmailAddresses).WithOne().HasForeignKey(x => x.ContactId).IsRequired();
      b.HasMany(x => x.ContactPhoneNumbers).WithOne().HasForeignKey(x => x.ContactId).IsRequired();
    });
    builder.Entity<ContactAddress>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "ContactAddresses" + MyProductConsts.DbSchema);
      b.ConfigureByConvention();

      //define composite key
      b.HasKey(x => new { x.ContactId, x.AddressId });

      //many-to-many configuration
      b.HasOne<Contact>().WithMany(x => x.ContactAddresses).HasForeignKey(x => x.ContactId).IsRequired();
      b.HasOne<Address>().WithMany().HasForeignKey(x => x.AddressId).IsRequired();

      b.HasIndex(x => new { x.ContactId, x.AddressId });
    });
    builder.Entity<ContactEmailAddress>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "ContactEmailAddresses" + MyProductConsts.DbSchema);
      b.ConfigureByConvention();

      //define composite key
      b.HasKey(x => new { x.ContactId, x.EmailAddressId });

      //many-to-many configuration
      b.HasOne<Contact>().WithMany(x => x.ContactEmailAddresses).HasForeignKey(x => x.ContactId).IsRequired();
      b.HasOne<EmailAddress>().WithMany().HasForeignKey(x => x.EmailAddressId).IsRequired();

      b.HasIndex(x => new { x.ContactId, x.EmailAddressId });
    });
    builder.Entity<ContactPhoneNumber>(b =>
    {
      b.ToTable(MyProductConsts.DbTablePrefix + "ContactPhoneNumbers" + MyProductConsts.DbSchema);
      b.ConfigureByConvention();

      //define composite key
      b.HasKey(x => new { x.ContactId, x.PhoneNumberId });

      //many-to-many configuration
      b.HasOne<Contact>().WithMany(x => x.ContactPhoneNumbers).HasForeignKey(x => x.ContactId).IsRequired();
      b.HasOne<PhoneNumber>().WithMany().HasForeignKey(x => x.PhoneNumberId).IsRequired();

      b.HasIndex(x => new { x.ContactId, x.PhoneNumberId });
    });
  }
}

联系方式

public class Contact : FullAuditedAggregateRoot<Guid>, IMultiTenant
{
  private Contact() { }

  internal Contact(Guid id, string name, [NotNull] string surname, string title,
    DateTime? lastContactDate, string note,
    Guid? departmentId = null, Guid? clientId = null, Guid? tenantId = null,
    bool primary = false, bool active = true)
  {
    Id = id;

    Check.Length(name, nameof(name), ContactConsts.NameMaxLength);
    Check.NotNull(surname, nameof(surname));
    Check.Length(surname, nameof(surname), ContactConsts.SurnameMaxLength);
    Check.Length(title, nameof(title), ContactConsts.TitleMaxLength);

    Name = name;
    Surname = surname;
    Title = title;
    LastContactDate = lastContactDate;
    Note = note;
    Primary = primary;
    Active = active;
    DepartmentId = departmentId;
    ClientId = clientId;
    TenantId = tenantId;
    ContactAddresses = new List<ContactAddress>();
    ContactEmailAddresses = new List<ContactEmailAddress>();
    ContactPhoneNumbers = new List<ContactPhoneNumber>();
  }

  [CanBeNull] public virtual string Name { get; set; }

  [NotNull] public virtual string Surname { get; private set; }

  [CanBeNull] public virtual string Title { get; set; }

  [CanBeNull] public virtual DateTime? LastContactDate { get; set; }

  [CanBeNull] public virtual string Note { get; set; }

  public virtual bool Primary { get; set; }

  public virtual bool Active { get; set; }

  [CanBeNull] public virtual Guid? DepartmentId { get; set; }
  [CanBeNull] public virtual Guid? ClientId { get; set; }

  [NotNull] public virtual ICollection<ContactAddress> ContactAddresses { get; }
  [NotNull] public virtual ICollection<ContactEmailAddress> ContactEmailAddresses { get; }
  [NotNull] public virtual ICollection<ContactPhoneNumber> ContactPhoneNumbers { get; }

  public virtual Guid? TenantId { get; set; }
}

联系地址

using System;
using Volo.Abp.Domain.Entities;

namespace MyProduct.Contacts;

public class ContactAddress : Entity
{
  private ContactAddress() { }

  public ContactAddress(Guid contactId, Guid addressId)
  {
    ContactId = contactId;
    AddressId = addressId;
  }

  public Guid ContactId { get; protected set; }

  public Guid AddressId { get; protected set; }

  public override object[] GetKeys()
  {
    return new object[] { ContactId, AddressId };
  }
}

ContactEmailAddress 和 ContactPhoneNumber 实体遵循与 ContactAddress 相同的模式。

地址

public class ContactPhoneNumber : Entity
{
  private ContactPhoneNumber() { }

  public ContactPhoneNumber(Guid contactId, Guid phoneNumberId)
  {
    ContactId = contactId;
    PhoneNumberId = phoneNumberId;
  }

  public Guid ContactId { get; protected set; }

  public Guid PhoneNumberId { get; protected set; }

  public override object[] GetKeys()
  {
    return new object[] { ContactId, PhoneNumberId };
  }
}

EmailAddress 和 PhoneNumber 实体遵循与地址相同的模式。

我可以提供额外的实体,但我认为这些是你需要的。

在使用查询语法(我不太熟悉)后,我使用流畅的语法重新编写了查询。

  protected virtual async Task<IQueryable<ContactWithNavigationProperties>> GetQueryForNavigationPropertiesAsync()
  {
    var dbContext = await GetDbContextAsync();
    return dbContext.Contacts
                    .Include(c => c.ContactAddresses)
                    .Include(c => c.ContactEmailAddresses)
                    .Include(c => c.ContactPhoneNumbers)
                    .AsSingleQuery()
                    .Select(c => new ContactWithNavigationProperties
                    {
                      Contact = c,
                      Department = dbContext.Departments.FirstOrDefault(d => d.Id == c.DepartmentId),
                      Client = dbContext.Clients.FirstOrDefault(cl => cl.Id == c.ClientId),
                      Addresses = dbContext.Addresses.Where(a =>
                        c.ContactAddresses.Select(ca => ca.AddressId).Contains(a.Id)).ToList(),
                      EmailAddresses = dbContext.EmailAddresses.Where(ea =>
                        c.ContactEmailAddresses.Select(cea => cea.EmailAddressId).Contains(ea.Id)).ToList(),
                      PhoneNumbers = dbContext.PhoneNumbers.Where(pn =>
                        c.ContactPhoneNumbers.Select(cpn => cpn.PhoneNumberId).Contains(pn.Id)).ToList()
                    });
  }

这如我所愿。

但是请注意,包含 AsSingleQuery() 调用。我收到一个异常,告诉我我的查询已配置(我想是全局的)使用 AsSplitQuery() 并且无法使用拆分查询检索查询中的集合。这是有道理的,因为内部查询使用包含的依赖项。

我认为某些事情与我的查询语法版本相同,这就是它失败的原因。

如果您对正在发生的事情或如何修复我的查询语法有很好的解释,请发表评论。希望大家有所收获。