EF Core 正在从抽象 class 创建 table
EF Core is creating table from abstract class
在 Asp.Net 核心 Web Api 上工作时,我试图让我的域模型像 possible.Therefore 一样干,我首先遵循 this link 创建一个基础实体我知道我的应用程序中需要的所有字段(我几乎粘贴了代码,所以我不打算再次粘贴它)。再工作一点之后,我想将文件上传添加到我的项目中。要意识到我创建了两个 Classes,分别称为 Photo 和 TextFile:
public class TextFile : File
{
#region Members
/// <summary>
/// The ForeignKey to the User
/// </summary>
public Guid UserId { get; private set; }
/// <summary>
/// The NavigationProperty to the User that added this Photo.
/// </summary>
public virtual User User { get; set; }
#endregion
#region Constructors
/// <summary>
/// For Ef Core
/// </summary>
private TextFile()
{ }
/// <summary>
/// Creates a new Instance of a TextFile.
/// </summary>
/// <param name="userId">The Id of the User that created this TextFile</param>
public TextFile(Guid userId)
{
UserId = userId;
}
#endregion
}
/// <summary>
/// Represents a Photo that got Uploaded
/// </summary>
public class Photo : File
{
#region Members
/// <summary>
/// Determines where this Image gets shown.
/// </summary>
public ImageOption? Option { get; private set; }
/// <summary>
/// The ForeignKey to the User
/// </summary>
public Guid UserId { get; private set; }
/// <summary>
/// The NavigationProperty to the User that added this Photo.
/// </summary>
public virtual User User { get; set; }
#endregion
#region Constructors
/// <summary>
/// For EF Core
/// </summary>
private Photo()
{ }
/// <summary>
/// Basic Constructor
/// </summary>
/// <param name="userId"></param>
public Photo(Guid userId)
{
UserId = userId;
}
#endregion
#region Methods
/// <summary>
/// Sets the Image Option only once
/// </summary>
/// <param name="option"></param>
public void SetImageOption(ImageOption option)
{
if (Option.HasValue)
return;
else
Option = option;
}
#endregion
}
这里我创建了一个名为 File 的抽象 class 因为我想避免重复使用相同的字段和 Methods.The File class 继承自 Entity(来自上面的文章)和具有文件名和文件大小等常见字段:
/// <summary>
/// Base Class for all Files
/// </summary>
public abstract class File : Entity<Guid>
{
#region Members
/// <summary>
/// The name of the File
/// </summary>
public string FileName { get; private set; }
/// <summary>
/// The Path to the File
/// </summary>
public string FilePath { get; private set; }
/// <summary>
/// The Size of the File
/// </summary>
public int FileSize { get; private set; }
public FileExtension Extension { get; private set; }
#endregion
#region Methods
/// <summary>
/// Creates a new Text File to be uploaded to the Database.
/// </summary>
/// <param name="file">The File to be Uploaded</param>
/// <param name="relativeFolderPath">The Relative Path from the WebRoot.</param>
/// <param name="userId">A UserId</param>
/// <param name="extension">The Extension of this File.</param>
/// <param name="token">A CancellationToken</param>
/// <returns></returns>
public static File CreateTextFile(IFormFile file, string relativeFolderPath, Guid userId, FileExtension extension, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var textFile = new TextFile(userId);
textFile.SetFileProperties(file, relativeFolderPath, extension, token);
return textFile;
}
/// <summary>
/// Creates a new Image File Model
/// </summary>
/// <param name="file">The File to be uploaded</param>
/// <param name="relativeFolderPath">The relative Path to the Folder this Image resides in.</param>
/// <param name="userId">A UserId</param>
/// <param name="extension">The File Extension</param>
/// <param name="token">A CancellationToken</param>
/// <returns></returns>
public static File CreatePhoto(IFormFile file, string relativeFolderPath, Guid userId, FileExtension extension, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var photo = new Photo(userId);
photo.SetFileProperties(file, relativeFolderPath, extension, token);
return photo;
}
/// <summary>
/// Set Properties on File Entity
/// </summary>
/// <param name="file">The File</param>
/// <param name="relativeFolderPath">The Path extending from the WebRoot</param>
/// <param name="extension">The File Extension</param>
/// <param name="token">A CancellationToken</param>
private void SetFileProperties(IFormFile file, string relativeFolderPath, FileExtension extension, CancellationToken token)
{
if(file == null)
throw new ArgumentNullException(nameof(file));
if(string.IsNullOrWhiteSpace(relativeFolderPath))
throw new ArgumentNullException(nameof(relativeFolderPath));
token.ThrowIfCancellationRequested();
FileSize = (int) file.Length;
Extension = extension;
FileName = Guid.NewGuid() + "." + extension.ToString().ToLower();
FilePath = Path.Combine(relativeFolderPath, FileName);
}
/// <summary>
/// Sets the Extension of this File
/// </summary>
/// <param name="extension"></param>
/// <param name="ext">The Extension of the File</param>
/// <param name="token">A CancellationToken</param>
private static void FindExtension(string extension, out FileExtension ext, CancellationToken token)
{
token.ThrowIfCancellationRequested();
switch (extension.ToLower())
{
case ".jpg":
ext = FileExtension.Jpg;
break;
case ".jpeg":
ext = FileExtension.Jpeg;
break;
case ".png":
ext = FileExtension.Png;
break;
case ".bmp":
ext = FileExtension.Bmp;
break;
case ".gif":
ext = FileExtension.Gif;
break;
case ".tif":
ext = FileExtension.Tif;
break;
case ".tiff":
ext = FileExtension.Tiff;
break;
case ".svg":
ext = FileExtension.Svg;
break;
case ".doc":
ext = FileExtension.Doc;
break;
case ".docx":
ext = FileExtension.Docx;
break;
case ".odt":
ext = FileExtension.Odt;
break;
case ".rtf":
ext = FileExtension.Rtf;
break;
case ".txt":
ext = FileExtension.Txt;
break;
case "xls":
ext = FileExtension.Xls;
break;
case ".xlsx":
ext = FileExtension.Xlsx;
break;
case ".ppt":
ext = FileExtension.Ppt;
break;
case ".pptx":
ext = FileExtension.Pptx;
break;
case ".pdf":
ext = FileExtension.Pdf;
break;
default:
throw new InvalidFileExtensionException($"The Extension {extension.ToLower()} is not allowed.");
}
}
/// <summary>
/// Determines if the Specified Extension is a allowed Extension.
/// Returns true in case the extension is a file extension.
/// Returns False in case the Extension is a Image File.
/// The FileExtension Parameter is always set
/// </summary>
/// <param name="extensionName">The extension as a string</param>
/// <param name="extension">The Extension that this File has.</param>
/// <param name="token">A CancellationToken</param>
/// <returns></returns>
public static bool IsTextFile(string extensionName, out FileExtension extension, CancellationToken token)
{
token.ThrowIfCancellationRequested();
FindExtension(extensionName, out extension, token);
return (int) extension > 8;
}
/// <summary>
/// Determines if the Extension is a allowed Extension and a Image File.
/// the Extension will always be set.
/// </summary>
/// <param name="extensionName">The Extension as string</param>
/// <param name="extension">The FileExtension</param>
/// <param name="token">A CancellationToken</param>
/// <returns></returns>
public static bool IsImageFile(string extensionName, out FileExtension extension, CancellationToken token)
{
token.ThrowIfCancellationRequested();
FindExtension(extensionName, out extension, token);
return (int)extension < 8;
}
#endregion
}
这就是我的困境开始的地方:当我尝试应用代码优先迁移时,我在迁移中得到以下信息:
migrationBuilder.CreateTable(
name: "Files",
columns: table => new
{
Id = table.Column<Guid>(nullable: false),
Created = table.Column<DateTime>(nullable: true),
LastModified = table.Column<DateTime>(nullable: true),
FileName = table.Column<string>(nullable: true),
FilePath = table.Column<string>(nullable: true),
FileSize = table.Column<int>(nullable: false),
Extension = table.Column<int>(nullable: false),
UserId = table.Column<Guid>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Files", x => x.Id);
table.ForeignKey(
name: "FK_Files_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Files_UserId",
table: "Files",
column: "UserId")
这不应该发生,因为我只想将派生的 classes 作为 Tables 而不是我的基础 Class。我已经尝试在我的 OnModelCreating 中使用 Ignore on Modelbuilder 解决它:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfiguration(new UserRoleConfiguration());
builder.ApplyConfiguration(new UserTokenConfiguration());
builder.Ignore<File>();
builder.Entity<User>().OwnsOne(x => x.FullName, fullName =>
{
fullName.OwnsOne(x => x.FirstName, firstName =>
{
firstName.Property(p => p.FirstNamePart).HasColumnName("FirstName_FirstPart").HasMaxLength(255)
.IsRequired();
firstName.Property(p => p.NameSeperator).HasColumnName("FirstName_NameSeperator").HasMaxLength(5);
firstName.Property(p => p.LastNamePart).HasColumnName("FirstName_LastPart").HasMaxLength(255);
});
fullName.OwnsOne(x => x.LastName, lastName =>
{
lastName.Property(p => p.FirstNamePart).HasColumnName("LastName_FirstPart").HasMaxLength(255)
.IsRequired();
lastName.Property(p => p.NameSeperator).HasColumnName("LastName_NameSeperator").HasMaxLength(5);
lastName.Property(p => p.LastNamePart).HasColumnName("LastName_LastPart").HasMaxLength(255);
});
});
builder.ApplyAllConfigurations();
}
但是输出还是一样。因此我想问如何解决这个问题,这样我就可以在数据库中只有派生的 classes 而不是抽象的 class.
我在此处发布您要求的代码:
/// <summary>
/// The User of this Application.
/// </summary>
public class User : Entity<Guid>
{
/// <summary>
/// Basic Constructor for the User
/// </summary>
public User()
{
UserRoles = new HashSet<UserRole>();
UserClaims = new HashSet<UserClaim>();
Tokens = new HashSet<UserToken>();
Photos = new HashSet<Photo>();
Files = new HashSet<TextFile>();
}
/// <summary>
/// A Concurrency Stamp
/// </summary>
public string ConcurrencyStamp { get; set; }
/// <summary>
/// The Email of this User
/// </summary>
public string Email { get; set; }
/// <summary>
/// The Normalized Email of this User
/// </summary>
public string NormalizedEmail { get; set; }
/// <summary>
/// Flag that indicates if the User has Confirmed his Email.
/// </summary>
public bool EmailConfirmed { get; set; }
/// <summary>
/// The User Name of this User.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The normalized User Name
/// </summary>
public string NormalizedUsername { get; set; }
/// <summary>
/// The hashed and salted Password.
/// </summary>
public string PasswordHash { get; set; }
/// <summary>
/// A Security Stamp to validate The Users Information
/// </summary>
public string SecurityStamp { get; set; }
/// <summary>
/// The Full Name of a User.
/// </summary>
public FullName FullName { get; set; }
/// <summary>
/// The specific Y-Number that identifies the User
/// </summary>
public string YNumberId { get; set; }
/// <summary>
/// The YNumber of this User.
/// </summary>
public YNumber YNumber { get; set; }
/// <summary>
/// The Collection of Roles.
/// </summary>
public virtual ICollection<UserRole> UserRoles { get; }
/// <summary>
/// The Collection of User Claims.
/// </summary>
public virtual ICollection<UserClaim> UserClaims { get; }
public virtual ICollection<UserToken> Tokens { get; }
public virtual ICollection<Photo> Photos { get; }
public virtual ICollection<TextFile> Files { get; }
}
/// <summary>
/// Applies all Configurations in this Assembly to the specified ModelBuilder Instance.
/// </summary>
/// <param name="modelBuilder">The Instance of the ModelBuilder that configures the Database.</param>
public static void ApplyAllConfigurations(this ModelBuilder modelBuilder)
{
var applyConfigurationMethodInfo = modelBuilder
.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.First(method => method
.Name
.Equals("ApplyConfiguration", StringComparison.OrdinalIgnoreCase));
var ret = typeof(ApplicationDbContext)
.Assembly
.GetTypes()
.Select(type =>
(type, i: type
.GetInterfaces()
.FirstOrDefault(i => i
.Name
.Equals(typeof(IEntityTypeConfiguration<>)
.Name, StringComparison.OrdinalIgnoreCase))))
.Where(it => it.i != null)
.Select(it => (et: it.i.GetGenericArguments()[0], configObject: Activator.CreateInstance(it.Item1)))
.Select(it =>
applyConfigurationMethodInfo.MakeGenericMethod(it.et)
.Invoke(modelBuilder, new[] {it.configObject}));
}
在检查我的代码时,我感觉错误不直接在迁移中,而是在应用我的配置的方法中。我在想,因为 TextFile Class 不在 Db 中(我试图通过此迁移添加它)并且应该创建的文件 Table 具有 TextFile [=41] 的所有字段=].只是名字错了。我对 TextFile class 的配置如下所示:
public class TextFileConfiguration : IEntityTypeConfiguration<TextFile>
{
public void Configure(EntityTypeBuilder<TextFile> builder)
{
//Set Primary Key
builder
.HasKey(x => x.Id);
//Add ValueGeneration
builder
.Property(x => x.Id)
.UseSqlServerIdentityColumn();
//Set Table Name
builder
.ToTable("TextFiles");
//Make Filename Required with MaxLength of 50 (because filename = Guid + FileExtension)
builder
.Property(x => x.FileName)
.IsRequired()
.HasMaxLength(50);
//Configure Inverse Navigation Property.
builder
.HasOne(x => x.User)
.WithMany(y => y.Files)
.HasForeignKey(z => z.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
会不会是我的配置没有得到应用?
很好很好,
在惩罚自己没有足够准确地阅读我以前的迁移之后,现在我回到回答我的问题:
我尝试将以下行直接添加到 OnModelCreating 中:
builder.Entity<TextFile>().ToTable("Text Files");
和魔法:
migrationBuilder.CreateTable(
name: "Text Files",
columns: table => new
{
Id = table.Column<Guid>(nullable: false),
Created = table.Column<DateTime>(nullable: true),
LastModified = table.Column<DateTime>(nullable: true),
FileName = table.Column<string>(nullable: true),
FilePath = table.Column<string>(nullable: true),
FileSize = table.Column<int>(nullable: false),
Extension = table.Column<int>(nullable: false),
UserId = table.Column<Guid>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Files", x => x.Id);
table.ForeignKey(
name: "FK_Files_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Files_UserId",
table: "Files",
column: "UserId")
迁移是正确的。因此,我检查了我的 ApplyConfiguration 并意识到它根本没有应用任何配置。因此我将其更改为:
var implementedConfigTypes = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => !t.IsAbstract
&& !t.IsGenericTypeDefinition
&& t.GetTypeInfo().ImplementedInterfaces.Any(i =>
i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)));
foreach (var configType in implementedConfigTypes)
{
dynamic config = Activator.CreateInstance(configType);
modelBuilder.ApplyConfiguration(config);
}
Courtesy of this Question on SO
这就是现在应用所有配置,这意味着我得到了所有表的正确名称和字段数量。
感谢 Ivan Stoev 试图解决这个问题。您的评论给了我正确的提示。
在 Asp.Net 核心 Web Api 上工作时,我试图让我的域模型像 possible.Therefore 一样干,我首先遵循 this link 创建一个基础实体我知道我的应用程序中需要的所有字段(我几乎粘贴了代码,所以我不打算再次粘贴它)。再工作一点之后,我想将文件上传添加到我的项目中。要意识到我创建了两个 Classes,分别称为 Photo 和 TextFile:
public class TextFile : File
{
#region Members
/// <summary>
/// The ForeignKey to the User
/// </summary>
public Guid UserId { get; private set; }
/// <summary>
/// The NavigationProperty to the User that added this Photo.
/// </summary>
public virtual User User { get; set; }
#endregion
#region Constructors
/// <summary>
/// For Ef Core
/// </summary>
private TextFile()
{ }
/// <summary>
/// Creates a new Instance of a TextFile.
/// </summary>
/// <param name="userId">The Id of the User that created this TextFile</param>
public TextFile(Guid userId)
{
UserId = userId;
}
#endregion
}
/// <summary>
/// Represents a Photo that got Uploaded
/// </summary>
public class Photo : File
{
#region Members
/// <summary>
/// Determines where this Image gets shown.
/// </summary>
public ImageOption? Option { get; private set; }
/// <summary>
/// The ForeignKey to the User
/// </summary>
public Guid UserId { get; private set; }
/// <summary>
/// The NavigationProperty to the User that added this Photo.
/// </summary>
public virtual User User { get; set; }
#endregion
#region Constructors
/// <summary>
/// For EF Core
/// </summary>
private Photo()
{ }
/// <summary>
/// Basic Constructor
/// </summary>
/// <param name="userId"></param>
public Photo(Guid userId)
{
UserId = userId;
}
#endregion
#region Methods
/// <summary>
/// Sets the Image Option only once
/// </summary>
/// <param name="option"></param>
public void SetImageOption(ImageOption option)
{
if (Option.HasValue)
return;
else
Option = option;
}
#endregion
}
这里我创建了一个名为 File 的抽象 class 因为我想避免重复使用相同的字段和 Methods.The File class 继承自 Entity(来自上面的文章)和具有文件名和文件大小等常见字段:
/// <summary>
/// Base Class for all Files
/// </summary>
public abstract class File : Entity<Guid>
{
#region Members
/// <summary>
/// The name of the File
/// </summary>
public string FileName { get; private set; }
/// <summary>
/// The Path to the File
/// </summary>
public string FilePath { get; private set; }
/// <summary>
/// The Size of the File
/// </summary>
public int FileSize { get; private set; }
public FileExtension Extension { get; private set; }
#endregion
#region Methods
/// <summary>
/// Creates a new Text File to be uploaded to the Database.
/// </summary>
/// <param name="file">The File to be Uploaded</param>
/// <param name="relativeFolderPath">The Relative Path from the WebRoot.</param>
/// <param name="userId">A UserId</param>
/// <param name="extension">The Extension of this File.</param>
/// <param name="token">A CancellationToken</param>
/// <returns></returns>
public static File CreateTextFile(IFormFile file, string relativeFolderPath, Guid userId, FileExtension extension, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var textFile = new TextFile(userId);
textFile.SetFileProperties(file, relativeFolderPath, extension, token);
return textFile;
}
/// <summary>
/// Creates a new Image File Model
/// </summary>
/// <param name="file">The File to be uploaded</param>
/// <param name="relativeFolderPath">The relative Path to the Folder this Image resides in.</param>
/// <param name="userId">A UserId</param>
/// <param name="extension">The File Extension</param>
/// <param name="token">A CancellationToken</param>
/// <returns></returns>
public static File CreatePhoto(IFormFile file, string relativeFolderPath, Guid userId, FileExtension extension, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var photo = new Photo(userId);
photo.SetFileProperties(file, relativeFolderPath, extension, token);
return photo;
}
/// <summary>
/// Set Properties on File Entity
/// </summary>
/// <param name="file">The File</param>
/// <param name="relativeFolderPath">The Path extending from the WebRoot</param>
/// <param name="extension">The File Extension</param>
/// <param name="token">A CancellationToken</param>
private void SetFileProperties(IFormFile file, string relativeFolderPath, FileExtension extension, CancellationToken token)
{
if(file == null)
throw new ArgumentNullException(nameof(file));
if(string.IsNullOrWhiteSpace(relativeFolderPath))
throw new ArgumentNullException(nameof(relativeFolderPath));
token.ThrowIfCancellationRequested();
FileSize = (int) file.Length;
Extension = extension;
FileName = Guid.NewGuid() + "." + extension.ToString().ToLower();
FilePath = Path.Combine(relativeFolderPath, FileName);
}
/// <summary>
/// Sets the Extension of this File
/// </summary>
/// <param name="extension"></param>
/// <param name="ext">The Extension of the File</param>
/// <param name="token">A CancellationToken</param>
private static void FindExtension(string extension, out FileExtension ext, CancellationToken token)
{
token.ThrowIfCancellationRequested();
switch (extension.ToLower())
{
case ".jpg":
ext = FileExtension.Jpg;
break;
case ".jpeg":
ext = FileExtension.Jpeg;
break;
case ".png":
ext = FileExtension.Png;
break;
case ".bmp":
ext = FileExtension.Bmp;
break;
case ".gif":
ext = FileExtension.Gif;
break;
case ".tif":
ext = FileExtension.Tif;
break;
case ".tiff":
ext = FileExtension.Tiff;
break;
case ".svg":
ext = FileExtension.Svg;
break;
case ".doc":
ext = FileExtension.Doc;
break;
case ".docx":
ext = FileExtension.Docx;
break;
case ".odt":
ext = FileExtension.Odt;
break;
case ".rtf":
ext = FileExtension.Rtf;
break;
case ".txt":
ext = FileExtension.Txt;
break;
case "xls":
ext = FileExtension.Xls;
break;
case ".xlsx":
ext = FileExtension.Xlsx;
break;
case ".ppt":
ext = FileExtension.Ppt;
break;
case ".pptx":
ext = FileExtension.Pptx;
break;
case ".pdf":
ext = FileExtension.Pdf;
break;
default:
throw new InvalidFileExtensionException($"The Extension {extension.ToLower()} is not allowed.");
}
}
/// <summary>
/// Determines if the Specified Extension is a allowed Extension.
/// Returns true in case the extension is a file extension.
/// Returns False in case the Extension is a Image File.
/// The FileExtension Parameter is always set
/// </summary>
/// <param name="extensionName">The extension as a string</param>
/// <param name="extension">The Extension that this File has.</param>
/// <param name="token">A CancellationToken</param>
/// <returns></returns>
public static bool IsTextFile(string extensionName, out FileExtension extension, CancellationToken token)
{
token.ThrowIfCancellationRequested();
FindExtension(extensionName, out extension, token);
return (int) extension > 8;
}
/// <summary>
/// Determines if the Extension is a allowed Extension and a Image File.
/// the Extension will always be set.
/// </summary>
/// <param name="extensionName">The Extension as string</param>
/// <param name="extension">The FileExtension</param>
/// <param name="token">A CancellationToken</param>
/// <returns></returns>
public static bool IsImageFile(string extensionName, out FileExtension extension, CancellationToken token)
{
token.ThrowIfCancellationRequested();
FindExtension(extensionName, out extension, token);
return (int)extension < 8;
}
#endregion
}
这就是我的困境开始的地方:当我尝试应用代码优先迁移时,我在迁移中得到以下信息:
migrationBuilder.CreateTable(
name: "Files",
columns: table => new
{
Id = table.Column<Guid>(nullable: false),
Created = table.Column<DateTime>(nullable: true),
LastModified = table.Column<DateTime>(nullable: true),
FileName = table.Column<string>(nullable: true),
FilePath = table.Column<string>(nullable: true),
FileSize = table.Column<int>(nullable: false),
Extension = table.Column<int>(nullable: false),
UserId = table.Column<Guid>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Files", x => x.Id);
table.ForeignKey(
name: "FK_Files_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Files_UserId",
table: "Files",
column: "UserId")
这不应该发生,因为我只想将派生的 classes 作为 Tables 而不是我的基础 Class。我已经尝试在我的 OnModelCreating 中使用 Ignore on Modelbuilder 解决它:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfiguration(new UserRoleConfiguration());
builder.ApplyConfiguration(new UserTokenConfiguration());
builder.Ignore<File>();
builder.Entity<User>().OwnsOne(x => x.FullName, fullName =>
{
fullName.OwnsOne(x => x.FirstName, firstName =>
{
firstName.Property(p => p.FirstNamePart).HasColumnName("FirstName_FirstPart").HasMaxLength(255)
.IsRequired();
firstName.Property(p => p.NameSeperator).HasColumnName("FirstName_NameSeperator").HasMaxLength(5);
firstName.Property(p => p.LastNamePart).HasColumnName("FirstName_LastPart").HasMaxLength(255);
});
fullName.OwnsOne(x => x.LastName, lastName =>
{
lastName.Property(p => p.FirstNamePart).HasColumnName("LastName_FirstPart").HasMaxLength(255)
.IsRequired();
lastName.Property(p => p.NameSeperator).HasColumnName("LastName_NameSeperator").HasMaxLength(5);
lastName.Property(p => p.LastNamePart).HasColumnName("LastName_LastPart").HasMaxLength(255);
});
});
builder.ApplyAllConfigurations();
}
但是输出还是一样。因此我想问如何解决这个问题,这样我就可以在数据库中只有派生的 classes 而不是抽象的 class.
我在此处发布您要求的代码:
/// <summary>
/// The User of this Application.
/// </summary>
public class User : Entity<Guid>
{
/// <summary>
/// Basic Constructor for the User
/// </summary>
public User()
{
UserRoles = new HashSet<UserRole>();
UserClaims = new HashSet<UserClaim>();
Tokens = new HashSet<UserToken>();
Photos = new HashSet<Photo>();
Files = new HashSet<TextFile>();
}
/// <summary>
/// A Concurrency Stamp
/// </summary>
public string ConcurrencyStamp { get; set; }
/// <summary>
/// The Email of this User
/// </summary>
public string Email { get; set; }
/// <summary>
/// The Normalized Email of this User
/// </summary>
public string NormalizedEmail { get; set; }
/// <summary>
/// Flag that indicates if the User has Confirmed his Email.
/// </summary>
public bool EmailConfirmed { get; set; }
/// <summary>
/// The User Name of this User.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The normalized User Name
/// </summary>
public string NormalizedUsername { get; set; }
/// <summary>
/// The hashed and salted Password.
/// </summary>
public string PasswordHash { get; set; }
/// <summary>
/// A Security Stamp to validate The Users Information
/// </summary>
public string SecurityStamp { get; set; }
/// <summary>
/// The Full Name of a User.
/// </summary>
public FullName FullName { get; set; }
/// <summary>
/// The specific Y-Number that identifies the User
/// </summary>
public string YNumberId { get; set; }
/// <summary>
/// The YNumber of this User.
/// </summary>
public YNumber YNumber { get; set; }
/// <summary>
/// The Collection of Roles.
/// </summary>
public virtual ICollection<UserRole> UserRoles { get; }
/// <summary>
/// The Collection of User Claims.
/// </summary>
public virtual ICollection<UserClaim> UserClaims { get; }
public virtual ICollection<UserToken> Tokens { get; }
public virtual ICollection<Photo> Photos { get; }
public virtual ICollection<TextFile> Files { get; }
}
/// <summary>
/// Applies all Configurations in this Assembly to the specified ModelBuilder Instance.
/// </summary>
/// <param name="modelBuilder">The Instance of the ModelBuilder that configures the Database.</param>
public static void ApplyAllConfigurations(this ModelBuilder modelBuilder)
{
var applyConfigurationMethodInfo = modelBuilder
.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.First(method => method
.Name
.Equals("ApplyConfiguration", StringComparison.OrdinalIgnoreCase));
var ret = typeof(ApplicationDbContext)
.Assembly
.GetTypes()
.Select(type =>
(type, i: type
.GetInterfaces()
.FirstOrDefault(i => i
.Name
.Equals(typeof(IEntityTypeConfiguration<>)
.Name, StringComparison.OrdinalIgnoreCase))))
.Where(it => it.i != null)
.Select(it => (et: it.i.GetGenericArguments()[0], configObject: Activator.CreateInstance(it.Item1)))
.Select(it =>
applyConfigurationMethodInfo.MakeGenericMethod(it.et)
.Invoke(modelBuilder, new[] {it.configObject}));
}
在检查我的代码时,我感觉错误不直接在迁移中,而是在应用我的配置的方法中。我在想,因为 TextFile Class 不在 Db 中(我试图通过此迁移添加它)并且应该创建的文件 Table 具有 TextFile [=41] 的所有字段=].只是名字错了。我对 TextFile class 的配置如下所示:
public class TextFileConfiguration : IEntityTypeConfiguration<TextFile>
{
public void Configure(EntityTypeBuilder<TextFile> builder)
{
//Set Primary Key
builder
.HasKey(x => x.Id);
//Add ValueGeneration
builder
.Property(x => x.Id)
.UseSqlServerIdentityColumn();
//Set Table Name
builder
.ToTable("TextFiles");
//Make Filename Required with MaxLength of 50 (because filename = Guid + FileExtension)
builder
.Property(x => x.FileName)
.IsRequired()
.HasMaxLength(50);
//Configure Inverse Navigation Property.
builder
.HasOne(x => x.User)
.WithMany(y => y.Files)
.HasForeignKey(z => z.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
会不会是我的配置没有得到应用?
很好很好,
在惩罚自己没有足够准确地阅读我以前的迁移之后,现在我回到回答我的问题:
我尝试将以下行直接添加到 OnModelCreating 中:
builder.Entity<TextFile>().ToTable("Text Files");
和魔法:
migrationBuilder.CreateTable(
name: "Text Files",
columns: table => new
{
Id = table.Column<Guid>(nullable: false),
Created = table.Column<DateTime>(nullable: true),
LastModified = table.Column<DateTime>(nullable: true),
FileName = table.Column<string>(nullable: true),
FilePath = table.Column<string>(nullable: true),
FileSize = table.Column<int>(nullable: false),
Extension = table.Column<int>(nullable: false),
UserId = table.Column<Guid>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Files", x => x.Id);
table.ForeignKey(
name: "FK_Files_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Files_UserId",
table: "Files",
column: "UserId")
迁移是正确的。因此,我检查了我的 ApplyConfiguration 并意识到它根本没有应用任何配置。因此我将其更改为:
var implementedConfigTypes = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => !t.IsAbstract
&& !t.IsGenericTypeDefinition
&& t.GetTypeInfo().ImplementedInterfaces.Any(i =>
i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)));
foreach (var configType in implementedConfigTypes)
{
dynamic config = Activator.CreateInstance(configType);
modelBuilder.ApplyConfiguration(config);
}
Courtesy of this Question on SO
这就是现在应用所有配置,这意味着我得到了所有表的正确名称和字段数量。
感谢 Ivan Stoev 试图解决这个问题。您的评论给了我正确的提示。