Отношение к таблице в EF Core 3.1 - PullRequest
1 голос
/ 07 февраля 2020

Использование SQL Сервер с Entity Framework Core 3.1. У меня есть мнение, что мне нужно присоединиться к столу. Это, очевидно, прекрасно работает на SQL сервере. Но я не могу правильно определить отношения в EF. По логике это должно быть возможно. Я не нашел онлайн-документации, которая бы указала, что это невозможно.

Я создал небольшой проект VisualStudio, в котором обнаружена проблема. Это все файлы в проекте. Это придумано, но очень просто. Я использую «базу данных сначала», поэтому этот пример соответствует моему реальному приложению.

Две сущности, которые сопоставляются с таблицами:

using System;
using System.Collections.Generic;

namespace ViewJoinIssue.Entities
{
    public class ArticleEntity
    {
        public int ArticleId { get; set; }

        public string Title { get; set; }

        public string Author { get; set; }

        public DateTime PublishDate { get; set; }

        public ICollection<ArticleTagEntity> Tags { get; set; } = new List<ArticleTagEntity>();
    }
}
namespace ViewJoinIssue.Entities
{
    public class ArticleTagEntity
    {
        public int ArticleTagId { get; set; }

        public string Tag { get; set; }

        public int ArticleId { get; set; }
        public ArticleEntity Article { get; set; }
    }
}

Другой класс для элемента, возвращаемого запросом в представлении:

using System.Collections.Generic;

namespace ViewJoinIssue.Entities
{
    public class ArticleViewItem
    {
        public int ArticleId { get; set; }

        public string Title { get; set; }

        public ICollection<ArticleTagEntity> Tags { get; set; } = new List<ArticleTagEntity>();
    }
}

Классы конфигурации для каждого из них:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ViewJoinIssue.Entities;

namespace ViewJoinIssue.EntityConfiguration
{
    public class ArticleConfiguration : IEntityTypeConfiguration<ArticleEntity>
    {
        public void Configure(EntityTypeBuilder<ArticleEntity> builder)
        {
            builder.HasKey(e => e.ArticleId);

            builder.Property(e => e.ArticleId)
                   .HasColumnName("ArticleId")
                   .HasColumnType("int")
                   .ValueGeneratedOnAdd();

            builder.Property(e => e.Title)
                   .HasColumnName("Title")
                   .HasColumnType("varchar(256)")
                   .HasMaxLength(256)
                   .IsRequired();

            builder.Property(e => e.Author)
                   .HasColumnName("Author")
                   .HasColumnType("varchar(64)")
                   .HasMaxLength(64)
                   .IsRequired();

            builder.Property(e => e.PublishDate)
                   .HasColumnName("PublishDate")
                   .HasColumnType("datetime")
                   .IsRequired();

            builder.ToTable("Article");

            builder.HasMany(e => e.Tags)
                   .WithOne(e => e.Article)
                   .HasForeignKey(e => e.ArticleId);
        }
    }
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ViewJoinIssue.Entities;

namespace ViewJoinIssue.EntityConfiguration
{
    public class ArticleTagConfiguration : IEntityTypeConfiguration<ArticleTagEntity>
    {
        public void Configure(EntityTypeBuilder<ArticleTagEntity> builder)
        {
            builder.HasKey(e => e.ArticleTagId);

            builder.Property(e => e.ArticleTagId)
                   .HasColumnName("ArticleTagId")
                   .HasColumnType("int")
                   .ValueGeneratedOnAdd();

            builder.Property(e => e.Tag)
                   .HasColumnName("Tag")
                   .HasColumnType("varchar(32)")
                   .HasMaxLength(32)
                   .IsRequired();

            builder.ToTable("ArticleTag");

            builder.HasOne(e => e.Article)
                   .WithMany(e => e.Tags)
                   .HasForeignKey(e => e.ArticleId);
        }
    }
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ViewJoinIssue.Entities;

namespace ViewJoinIssue.EntityConfiguration
{
    public class ArticleViewItemConfiguration : IEntityTypeConfiguration<ArticleViewItem>
    {
        public void Configure(EntityTypeBuilder<ArticleViewItem> builder)
        {
            builder.HasNoKey();

            builder.Property(e => e.ArticleId)
                   .HasColumnName("ArticleId")
                   .HasColumnType("int")
                   .ValueGeneratedOnAdd();

            builder.Property(e => e.Title)
                   .HasColumnName("Title")
                   .HasColumnType("varchar(256)")
                   .HasMaxLength(256)
                   .IsRequired();

            builder.ToView("vwArticles");

            builder.HasMany(e => e.Tags)
                   .WithOne()
                   .HasPrincipalKey(e => e.ArticleId);
        }
    }
}

Контекст базы данных:

using Microsoft.EntityFrameworkCore;
using ViewJoinIssue.Entities;
using ViewJoinIssue.EntityConfiguration;

namespace ViewJoinIssue
{
    public class DatabaseContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("Data Source=(local)\\SQLSERVER2017;Initial Catalog=Articles;Trusted_Connection=true;MultipleActiveResultSets=true;Packet Size=4096");

            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new ArticleConfiguration());
            modelBuilder.ApplyConfiguration(new ArticleTagConfiguration());
            modelBuilder.ApplyConfiguration(new ArticleViewItemConfiguration());

            base.OnModelCreating(modelBuilder);
        }

        public virtual DbSet<ArticleEntity> Articles { get; set; }
        public virtual DbSet<ArticleTagEntity> ArticleTags { get; set; }
        public virtual DbSet<ArticleViewItem> ArticleViewItems { get; set; }
    }
}

И простая консольная программа:

using System;
using System.Linq;

namespace ViewJoinIssue
{
    class Program
    {
        static void Main(string[] args)
        {
            var context = new DatabaseContext();

            var viewItems = context.ArticleViewItems.ToList();

            Console.WriteLine($"Found {viewItems.Count} items.");
        }
    }
}

Вы можете скомпилировать ее и запустить, чтобы увидеть ошибку, которую я получаю. Фактическая база данных не требуется.

Для этого оператора в ArticleViewItemConfiguration ...

            builder.HasMany(e => e.Tags)
                   .WithOne()
                   .HasPrincipalKey(e => e.ArticleId);

... он выдает это исключение:

   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.SetOrAddForeignKey(ForeignKey foreignKey, InternalEntityTypeBuilder principalEntityTypeBuilder, IReadOnlyList`1 dependentProperties, Key principalKey, String navigationToPrincipalName, Nullable`1 isRequired, Nullable`1 configurationSource)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.CreateForeignKey(InternalEntityTypeBuilder principalEntityTypeBuilder, IReadOnlyList`1 dependentProperties, Key principalKey, String navigationToPrincipalName, Nullable`1 isRequired, ConfigurationSource configurationSource)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.HasRelationshipInternal(EntityType targetEntityType, Key principalKey, ConfigurationSource configurationSource)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.HasRelationship(EntityType principalEntityType, ConfigurationSource configurationSource)
   at Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1.HasMany[TRelatedEntity](Expression`1 navigationExpression)
   at ViewJoinIssue.EntityConfiguration.ArticleViewItemConfiguration.Configure(EntityTypeBuilder`1 builder) in C:\Work\PostBidShip\Code\ViewJoinIssue\EntityConfiguration\ArticleViewItemConfiguration.cs:line 26
   at Microsoft.EntityFrameworkCore.ModelBuilder.ApplyConfiguration[TEntity](IEntityTypeConfiguration`1 configuration)
   at ViewJoinIssue.DatabaseContext.OnModelCreating(ModelBuilder modelBuilder) in C:\Work\PostBidShip\Code\ViewJoinIssue\DatabaseContext.cs:line 20
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_Model()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.CheckState()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Collections.Generic.IEnumerable<TEntity>.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at ViewJoinIssue.Program.Main(String[] args) in C:\Work\PostBidShip\Code\ViewJoinIssue\Program.cs:line 12

Похоже, что он пытается «установить или добавить» внешний ключ. Но это не будет работать с представлением, так как у него нет ключа.

Я удалил вызов метода ".HasPrincipleKey", но я все еще получаю то же исключение. Логично, что мне нужно сообщить EF свойство, которое будет использоваться для объединения, поскольку нет внешнего ключа (это представление).

Кто-нибудь может указать, что я делаю неправильно? Или, возможно, это ошибка EF. Я не нашел помощи онлайн; Я искал все возможные способы. Большинство примеров, использующих представление, очень просты; большинство только для того, чтобы вернуть счет.

Спасибо !!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...