Использование 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. Я не нашел помощи онлайн; Я искал все возможные способы. Большинство примеров, использующих представление, очень просты; большинство только для того, чтобы вернуть счет.
Спасибо !!