简体   繁体   English

Entity Framework Core 自定义脚手架

[英]Entity Framework Core Customize Scaffolding

I have done scaffolding of my SQLServer database.我已经完成了我的 SQLServer 数据库的脚手架。 And it creates the POCO objects in the specified folder.并在指定的文件夹中创建 POCO 对象。 What i would like to do is that it extends from my base class.我想做的是它从我的基类扩展而来。 I also use repository pattern so i need to have Id key on every entity and I don't want to change that each time i rescaffold the database.我还使用存储库模式,所以我需要在每个实体上都有 Id 键,我不想在每次重新构建数据库时都更改它。

Scaffold Model Example脚手架模型示例

public partial class Food
{
     public int Id { get; set; }
     public string Name { get; set; }
     public string Description { get; set; }
     public double Price { get; set; }
}

Expected Result:预期结果:

public partial class Food : EntityBase
{
     public string Name { get; set; }
     public string Description { get; set; }
     public double Price { get; set; }
}

public class EntityBase : IEntityBase
{
    public int Id { get; set; }
}

You can use DbContextWriter & EntityTypeWriter to customize scaffold output.您可以使用DbContextWriterEntityTypeWriter来自定义脚手架输出。

In newer versions of entity core writers renamed:在较新版本的实体核心编写器中重命名:

  • DBContextWriter ==>> CSharpDbContextGenerator DBContextWriter ==>> CSharpDbContextGenerator
  • EntityTypeWriter ==>> CSharpEntityTypeGenerator EntityTypeWriter ==>> CSharpEntityTypeGenerator

Write some custom type writer, you can override everything and you will get your own code generator:编写一些自定义类型编写器,您可以覆盖所有内容,您将获得自己的代码生成器:

//HERE YOU CAN CHANGE THE WAY TYPES ARE GENERATED AND YOU CAN ADD INTERFACE OR BASE CLASS AS PARENT.
public class CustomEntitiyTypeWriter : EntityTypeWriter
{
    public CustomEntitiyTypeWriter([NotNull] CSharpUtilities cSharpUtilities)
        : base(cSharpUtilities)
    { }

    // Write Code returns generated code for class and you can raplec it with your base class
    public override string WriteCode([NotNull] EntityConfiguration entityConfiguration)
    {
        var classStr = base.WriteCode(entityConfiguration);

        var defaultStr = "public partial class " + entityConfiguration.EntityType.Name;
        var baseStr = "public partial class " + entityConfiguration.EntityType.Name + " : EntityBase";

        classStr = classStr.Replace(defaultStr, baseStr);

        return classStr;
    }      
}

declare it in setup:在设置中声明它:

public static void ConfigureDesignTimeServices(IServiceCollection services)
           => services.AddSingleton<EntityTypeWriter, CustomEntitiyTypeWriter>();

and then scaffold db, you can do the same for DBContext with CustomDBContextWriter.然后脚手架 db,您可以使用 CustomDBContextWriter 对 DBContext 执行相同的操作。

@Tornike Choladze's excellent answer led me in the right direction, but in the latest versions of .Net Core (2.0 >) this has to be done a little differently it seems, with regard to the setup. @Tornike Choladze 的出色回答使我朝着正确的方向前进,但在最新版本的 .Net Core (2.0 >) 中,这似乎在设置方面有所不同。

The custom entity type generator:自定义实体类型生成器:

class MyEntityTypeGenerator : CSharpEntityTypeGenerator
{
    public MyEntityTypeGenerator(ICSharpUtilities cSharpUtilities) : base(cSharpUtilities) { }

    public override string WriteCode(IEntityType entityType, string @namespace, bool useDataAnnotations)
    {
        string code = base.WriteCode(entityType, @namespace, useDataAnnotations);

        var oldString = "public partial class " + entityType.Name;
        var newString = "public partial class " + entityType.Name + " : EntityBase";

        return code.Replace(oldString, newString);
    }
}

And the setup, which consists of a class in the same assembly and implementing IDesignTimeServices :以及由同一程序IDesignTimeServices的一个类组成并实现IDesignTimeServices

public class MyDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<ICSharpEntityTypeGenerator, MyEntityTypeGenerator>();            
    }
}

In case you want to modify entity names (and file and class names) here's something that might help:如果您想修改实体名称(以及文件和类名称),以下内容可能会有所帮助:

Based on Chris Peacock's answer (and comments) you can build two classes to modify the names of entities and files (this works in Core 2.2).根据 Chris Peacock 的回答(和评论),您可以构建两个类来修改实体和文件的名称(这适用于 Core 2.2)。

public class CustomEFUtilities : CSharpUtilities
{
    public override string Uniquifier(
        string proposedIdentifier, ICollection<string> existingIdentifiers)
    {
        var finalIdentifier = base.Uniquifier(proposedIdentifier, existingIdentifiers);
        
        // your changes here
        if (finalIdentifier.StartsWith("tl"))
        {
            finalIdentifier = finalIdentifier.Substring(2);
        }
        
        return finalIdentifier;
    }
}

And similarly:同样:

public class CustomEFDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<ICSharpUtilities, CustomEFUtilities>();
    }
}

Edit (EF Core 3.1)编辑(EF 核心 3.1)

A breaking change was introduced ( https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#microsoftentityframeworkcoredesign-is-now-a-developmentdependency-package ) so you need to modify your project file:引入了重大更改( https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/break-changes#microsoftentityframeworkcoredesign-is-now-a-developmentdependency- package ) 所以你需要修改你的项目文件:

If you need to reference this package to override EF Core's design-time behavior, then you can update PackageReference item metadata in your project.如果需要引用此包以覆盖 EF Core 的设计时行为,则可以更新项目中的 PackageReference 项元数据。

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0">
  <PrivateAssets>all</PrivateAssets>
  <!-- Remove IncludeAssets to allow compiling against the assembly -->
  <!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
</PackageReference>

Currently, the scaffolding tools do not support the scenario you describe.目前,脚手架工具不支持您描述的场景。 There are no options to customise their output, only the location of the generated files and whether to use Fluent API or data annotations for configuration.没有自定义输出的选项,只有生成文件的位置以及是否使用 Fluent API 或数据注释进行配置。

EF Core is a Code First framework. EF Core 是一个 Code First 框架。 The advice is that once you have reverse-engineered your model from the existing database, you use migrations to keep the two in sync with each other from then on.建议是,一旦您从现有数据库对模型进行逆向工程,您就可以使用迁移从那时起使两者保持同步。

Having said that, I realise that might not always be possible depending on how responsibilities are apportioned within teams.话虽如此,我意识到这可能并不总是可行的,这取决于团队内部的职责分配方式。 In that kind of case, you might want to consider opening an issue requesting this feature on the GitHub repo for EF Core: https://github.com/aspnet/EntityFramework .在这种情况下,您可能需要考虑在 EF Core 的 GitHub 存储库上打开一个请求此功能的问题: https : //github.com/aspnet/EntityFramework

In my scenario I wanted to prefix all table names with an underscore.在我的场景中,我想用下划线作为所有表名的前缀 Version: Core 2.2 .版本:核心 2.2
I don't use Id as name of my primary key column, instead table name + Id like this: FoodId .我不使用Id作为主键列的名称,而是像这样使用表名 + Id: FoodId This forced me to add the [Key] data annotation to the primary key.这迫使我将[Key]数据注释添加到主键。


Model型号
Scaffold Model Example:脚手架模型示例:

using System;
using System.Collections.Generic;

namespace MyNamespace
{
    public partial class Food
    {
         public int FoodId { get; set; }
         public string Name { get; set; }
         public string Description { get; set; }
         public double Price { get; set; }
    }
}

Expected Result:预期结果:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; //Add

namespace MyNamespace
{
    public partial class _Food //Prefix with underscore
    {
         [Key] //Add
         public int FoodId { get; set; }
         public string Name { get; set; }
         public string Description { get; set; }
         public double Price { get; set; }
    }
}


How I solved it我是如何解决的
I used a combination of @Chris Peacock's and @DavidC's answers.我结合了@Chris Peacock 和@DavidC 的回答。

1. Firstly, rename all entities 1.首先,重命名所有实体
To prevent renaming of properties I had to explicitly tell which unique identifiers to rename, because Uniquifier function doesn't tell which type it renames.为了防止特性我必须明确地告诉重新命名其唯一标识符,由于改名Uniquifier功能不告诉它重命名哪种类型。

public class MyCSharpUtilities : CSharpUtilities
{
    public override string Uniquifier(string proposedIdentifier, ICollection<string> existingIdentifiers)
    {
        var finalIdentifier = base.Uniquifier(proposedIdentifier, existingIdentifiers);

        //Prefix entity names with underscore
        if (finalIdentifier == "Food" ||
            finalIdentifier == "Fruit" ||
            finalIdentifier == "Vegetables")
        {
            finalIdentifier = "_" + finalIdentifier;
        }

        return finalIdentifier;
    }
}

2. Secondly, add the using statement and the [Key] 2.其次,添加using语句和[Key]

public class MyEntityTypeGenerator : CSharpEntityTypeGenerator
{
    public MyEntityTypeGenerator(ICSharpHelper cSharpHelper) : base(cSharpHelper) { }   

    public override string WriteCode(IEntityType entityType, string @namespace, bool useDataAnnotations)
    {
        string code = base.WriteCode(entityType, @namespace, useDataAnnotations);           
        string entityTypeName = entityType.Name.Replace("_", "");
        string keyPropertyString = "public int " + entityTypeName + "Id { get; set; }";

        if (code.Contains(keyPropertyString))
        {
            //Add "using" for data annotation
            string oldString = "using System.Collections.Generic;" + Environment.NewLine + Environment.NewLine + "namespace MyNamespace";
            string newString = "using System.Collections.Generic;" + Environment.NewLine + "using System.ComponentModel.DataAnnotations;" + Environment.NewLine + Environment.NewLine + "namespace MyNamespace";
            code = code.Replace(oldString, newString);

            //Add [Key] data annotation
            string newKeyPropertyString = "[Key]" + Environment.NewLine + "\t\t" + keyPropertyString;
            code = code.Replace(keyPropertyString, newKeyPropertyString);
        }

        return code;
    }
}

3. Finally, add these services to Startup.cs 3.最后,将这些服务添加到Startup.cs

public class CustomEFDesignTimeServices : IDesignTimeServices
{
    //Used for scaffolding database to code
    public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<ICSharpUtilities, MyCSharpUtilities>();
        serviceCollection.AddSingleton<ICSharpEntityTypeGenerator, MyEntityTypeGenerator>();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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