简体   繁体   中英

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

I am creating a somewhat complex query to retrieve some many to many relationships with an entity. Here is thee code that creates the query:

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

As soon as this query is executed, I am getting the following exception:

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)

So before I submit a bug, I was hoping someone who is an expert with Entity Framework could take a look at my query and point out anything glaring.

Here is my csproj file with its dependencies:

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

As you can see I am using ABP Commercial 4.4.4 with EF 5.0. Thanks for your help.

EDIT

It was requested that I include my DB context and my applicable entity definitions. Here goes:

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

Contact

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

ContactAddress

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 and ContactPhoneNumber entities follow the same pattern as ContactAddress.

Address

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 and PhoneNumber entities follow the same pattern as Address.

I can provide additional entities but I think these are the ones you need.

After struggling with the query syntax (which I'm not all that familiar with), I reworked the query using the fluent syntax.

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

This worked as I had hoped.

Note, however, the inclusion of the AsSingleQuery() call. I was getting an exception telling me that my query was configured (I suppose globally) to use AsSplitQuery() and that a collection within the query could not be retrieved using a split query. This makes some sense as the inner queries use the included dependencies.

I think something was going on along the same lines with my query syntax version and that's why it was failing.

Please leave a comment if you have a good explanation for what is going on or how to fix my query syntax. Hopefully everyone will learn something.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM