zoukankan      html  css  js  c++  java
  • Asp.net core 学习笔记 ( ef core )

    更新: 2021-06-17 

    5.0 之后多了一些不错的功能 

    https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew

    1.Split queries 

    _db.Subcategories.AsSplitQuery();
    _db.Subcategories.AsSingleQuery();

    include 太多的情况下, split query 可能性能会好一些. 所以 ef core 让我们自己调.

    也可以像 AsTracking 那样调 global 

    o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)

    2. Filtered include

    include 之前是写不了 filter 的. 现在总算可以了 

    var blogs = context.Blogs
        .Include(e => e.Posts.Where(p => p.Title.Contains("Cheese")))
        .ToList();

     3. Table-per-type (TPT) mapping

    这个也是 ef 6 就有的功能,终于有了

    4. Shadow and Indexer Properties

    https://docs.microsoft.com/en-us/ef/core/modeling/shadow-properties

    处理 c# class 里面没有属性,但是 sql 却有 column

    比如 n-n 的情况下, 中间表就是一个 c# 没有 class 但是 sql 有 table 的情况了. 

    5. Database collations

    可以指定 column 支持 case sensitive 了.

    6. many to many 

    以前是用 2 个 1-n 来实现的. 总算是有了. 

    配置的方法是 

    builder
        .HasMany(e => e.Subcategories)
        .WithMany(e => e.Products)
        .UsingEntity<Dictionary<string, object>>(
            "ProductSubcategory",
            e =>
            {
                e.Property<string>("MainCategory");
                e.Property<string>("Subcategory");
                return e
                .HasOne<Subcategory>()
                .WithMany()
                .HasForeignKey(new[] { "MainCategory", "Subcategory" })
                .HasPrincipalKey(e => new { e.MainCategory, e.SubcategoryName })
                .OnDelete(DeleteBehavior.Cascade);
            },
            e => e
                .HasOne<Product>()
                .WithMany()
                .HasForeignKey("Product")
                .HasPrincipalKey(e => e.ProductName)
                .OnDelete(DeleteBehavior.Cascade)
        );

    我这个是比较复杂的, 用到了 alt key 和 shadowns properties 的概念

    补充一点 ef core 默认行为, 中间 table 的 naming ProductCategory or CategoryProduct 

    ef core 是通过名字 a-z 排序来做的. 比如上面这个的话是 CategoryProduct 因为 C 在 P 之前 

    更新: 2020-11-04

    migration 添加 row version 去 exsiting table 的话, 需要手动把 defualt value 去掉 不然会有 error 哦

    refer: https://stackoverflow.com/questions/63703638/generating-non-nullable-rowversion-on-sql-server-with-ef-core-3-1

    更新: 2020-10-05

    ef core 5.0

    https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#rc1

    比较会用到的 : 

    https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#many-to-many (多对多)

    https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#stored-persisted-computed-columns (persisted computed column)

    https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#configure-database-precisionscale-in-model (decimal)

    https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#filtered-include (filter + include)

    更新: 2020-07-11

    今天才发现的坑

    sql select 的时候如果没有指定 orderby, 那么它是无法预测的. 并不是 order by Id asc 也不是依据 clustered. 我猜是看它有没有走索引之类的瓜.

    总之如果你需要次序,那么就一定要放 orderby

    https://stackoverflow.com/questions/26236352/default-row-order-in-select-query-sql-server-2008-vs-sql-2012

    ef core include 是不能 orderby 的

    https://stackoverflow.com/questions/15378136/entity-framework-ordering-includes

    ef core insert save change 的顺序是不一定的. 如果你要确保 insert 的顺序,那么最好的方式是进一条 save 一次.

    不要用 add range 也不要 add 多多了才 save. 

    https://github.com/dotnet/efcore/issues/7894

    更新 : 2020-02-28 

    乐观锁

    依据某些数据 -> 计算 -> 决定结果. 在这个过程中确保依赖数据不变,这个就是锁的作用. 

    比如一个客户跟我要了一个货,我得锁着给他,不可以给其它同事拿去卖. 一样的道理

    更新 2020-01-01

    当 nullable + unique 时, ef 会自动的帮我们添加 filter... 如果不希望它自作聪明的话,可以把它关了。

    clustered 就没有 filter 哦

    更新: 2019-11-17 

    ef core 没法 where COLLATE SQL_Latin1_General_CP1_CS_AS 区分大小写. 

    https://github.com/aspnet/EntityFrameworkCore/issues/8813

    https://github.com/aspnet/EntityFrameworkCore/issues/1222

    目前能做的就是 rawSql 或者在 column 上 set collate 

    alter table Products alter column name nvarchar(64) collate SQL_Latin1_General_CP1_CS_AS;
    alter table Products alter column name nvarchar(64) collate SQL_Latin1_General_CP1_CI_AS;

    更新: 2019-08-03

    rename table 有时候会很麻烦, migration 会删除表和从创。里面有资料就不方便了。

    可以手动写代码

    https://stackoverflow.com/questions/13296996/entity-framework-migrations-renaming-tables-and-columns

    public override void Up()
    {
        RenameTable("ReportSections", "ReportPages");
        RenameTable("ReportSectionGroups", "ReportSections");
        RenameColumn("ReportPages", "Group_Id", "Section_Id");
    }

    就如同我们跑 sql 语句类似了。

    use klc2;
    EXEC sp_rename 'TableSettings', 'UserSettings';
    EXEC sp_rename 'UserSettings.tableName', 'name', 'COLUMN'; 
    EXEC sp_rename 'UserSettings.settingJson', 'json', 'COLUMN'; -- INDEX 也是这个换法
    EXEC sp_rename 'dbo.PK_UserSettingsa', 'PK_UserSettings';  
    EXEC sp_rename 'dbo.FK_UserSettings_AspNetUsers_userIa', 'FK_UserSettings_AspNetUsers_userId';  
    
    -- 检查名字
    SELECT name, SCHEMA_NAME(schema_id) AS schema_name, type_desc  
    FROM sys.objects  
    WHERE parent_object_id = (OBJECT_ID('UserSettings'))   
    AND type IN ('C','F', 'PK');   

     直接跑 

       migrationBuilder.Sql(
                 @"
                    EXEC sp_rename 'TableSettings', 'UserSettings';
                    EXEC sp_rename 'UserSettings.tableName', 'name', 'COLUMN'; 
                    EXEC sp_rename 'UserSettings.settingJson', 'json', 'COLUMN'; 
                    EXEC sp_rename 'dbo.PK_TableSettings', 'PK_UserSettings';  
                    EXEC sp_rename 'dbo.FK_TableSettings_AspNetUsers_userId', 'FK_UserSettings_AspNetUsers_userId';  
                ");

     如果是要换 column length for 有 relation 的也是不可以用 ef core migrations, 去 sql management studio 换, 它会批量, 然后 migration 里面放 return 假假跑一下就可以了.

    还有 index 也是要换, 找的方式是 : 

    select * from (
    select i.[name] as index_name,
        substring(column_names, 1, len(column_names)-1) as [columns],
        case when i.[type] = 1 then 'Clustered index'
            when i.[type] = 2 then 'Nonclustered unique index'
            when i.[type] = 3 then 'XML index'
            when i.[type] = 4 then 'Spatial index'
            when i.[type] = 5 then 'Clustered columnstore index'
            when i.[type] = 6 then 'Nonclustered columnstore index'
            when i.[type] = 7 then 'Nonclustered hash index'
            end as index_type,
        case when i.is_unique = 1 then 'Unique'
            else 'Not unique' end as [unique],
        schema_name(t.schema_id) + '.' + t.[name] as table_view, 
        case when t.[type] = 'U' then 'Table'
            when t.[type] = 'V' then 'View'
            end as [object_type]
    from sys.objects t
        inner join sys.indexes i
            on t.object_id = i.object_id
        cross apply (select col.[name] + ', '
                        from sys.index_columns ic
                            inner join sys.columns col
                                on ic.object_id = col.object_id
                                and ic.column_id = col.column_id
                        where ic.object_id = t.object_id
                            and ic.index_id = i.index_id
                                order by key_ordinal
                                for xml path ('') ) D (column_names)
    where t.is_ms_shipped <> 1
    and index_id > 0 
    ) indexes where table_view = 'dbo.QuotationItems';

    修改记得哦是 dbo.tableName.indexName

    EXEC sp_rename N'dbo.QuotationItems.IX_DocumentItems_createdBy', N'IX_QuotationItems_createdBy', N'INDEX';  

    更新: 2019-06-12

    不小心踩坑

    var adidas = new Supplier { name = "adidas" };
    Db.Suppliers.Add(adidas);
    Db.SaveChanges(); // 关键
    Db.Products.Add(new Product
    {
        supplier = adidas,
        code = "001",
    // supplierId = adidas.Id }); Db.SaveChanges();

    这样会报错哦.

    因为第一个 savechange 已经把 adidas 有了 id 那么 product.add 时我又传入了 adidas

    那么 ef 会以为我要去创建, 但是它有 id 了啊, sql 就会报错了 (这里 id 是 auto increment 的情况)

    所以按上面的写法其实第一个 save change 是多余的, 如果第一个 save change 是必须的话,下面就应该 update foreign key 就好了。

    更新 : 2019-05-04 

    动态生成 fluent api 

    enum to string 这个方式我们可以通过 fluent api 来实现, 比如下面这样. 

    modelBuilder.Entity<Order>().Property(p => p.status).IsRequired().HasMaxLength(128)
        .HasConversion(
            v => v.ToString(),
            v => (OrderStatus)Enum.Parse(typeof(OrderStatus), v)
        );

    但是如果我们有非常多的 table 和 enum,每一个属性都写这么一行...累丫 

    那么我们可以通过反射来实现这个调用. 

    步骤如下,首先找出所有的 EntityType

    foreach (var entity in modelBuilder.Model.GetEntityTypes())

    接着把所有 enum property 找出来 

    var props = entity.ClrType.GetProperties()
        .Where(p => p.PropertyType.IsEnum)
        .ToList();

    然后每一个 prop 都要绑定 HasConversion 

    foreach (var prop in props)

    接着就是反射调用 

    // modelBuilder.Entity<Order>()
    var entityMethod = modelBuilder.GetType().GetMethod(nameof(ModelBuilder.Entity), 1, new Type[] { });
    entityMethod = entityMethod.MakeGenericMethod(new[] { entity.ClrType });
    var entityTypeBuilder = entityMethod.Invoke(modelBuilder, new object[] { });

    接着 

    // ...Property(entity => entity.status) 
    var paramType = typeof(Expression<>).MakeGenericType(new[] { typeof(Func<,>).MakeGenericType(new[] { entity.ClrType, prop.PropertyType }) });
    var propertyMethod = entityTypeBuilder.GetType().GetMethods()
        .Where(m => m.Name == nameof(EntityTypeBuilder.Property) && m.GetGenericArguments().Count() == 1)
        .Select(m => m.MakeGenericMethod(prop.PropertyType))
        .Single(m =>
        {
            var parameters = m.GetParameters();
            if (parameters.Count() != 1) return false;
            return parameters[0].ParameterType == paramType;
        });
    var paramEntityExp = Expression.Parameter(entity.ClrType, "entity");
    var getPropExp = Expression.Property(paramEntityExp, prop);
    var lambdaExp = Expression.Lambda(getPropExp, paramEntityExp);
    var propertyBuilder = propertyMethod.Invoke(entityTypeBuilder, new[] { lambdaExp });

    最后 

    // ...HasConversion(propertyValue => propertyValue.ToString(), sqlValue => (OrderStatus)Enum.Parse(typeof(OrderStatus), propertyValue));        
    var firstParamPropertyExp = Expression.Parameter(prop.PropertyType, "propertyValue");
    var firstParamCallMethod = typeof(Enum).GetMethod(nameof(Enum.ToString), new Type[] { });
    var firstParamCallMethodExp = Expression.Call(
        firstParamPropertyExp, firstParamCallMethod
    );
    var firstLambdaExp = Expression.Lambda(firstParamCallMethodExp, firstParamPropertyExp);
    
    
    var secondParamPropertyExp = Expression.Parameter(typeof(string), "sqlValue");
    var secondParamCallMethod = typeof(Enum).GetMethod(nameof(Enum.Parse), new[] { typeof(Type), typeof(string) });
    var secondParamCallMethodExp = Expression.Call(
            secondParamCallMethod, new Expression[] { Expression.Constant(prop.PropertyType), secondParamPropertyExp }
        );
    var secondParamConvertExp = Expression.Convert(
        secondParamCallMethodExp,
        prop.PropertyType
    );
    var secondLambdaExp = Expression.Lambda(secondParamConvertExp, secondParamPropertyExp);
    
    var firstParamType = typeof(Expression<>).MakeGenericType(new[] { typeof(Func<,>).MakeGenericType(new[] { prop.PropertyType, typeof(string) }) });
    var secondParamType = typeof(Expression<>).MakeGenericType(new[] { typeof(Func<,>).MakeGenericType(new[] { typeof(string), prop.PropertyType }) });
    var hasConversionMethod = propertyBuilder.GetType().GetMethods()
        .Where(m => m.Name == nameof(PropertyBuilder.HasConversion) && m.GetGenericArguments().Count() == 1)
        .Select(m => m.MakeGenericMethod(typeof(string)))
        .Single(m =>
        {
            var parameters = m.GetParameters();
            if (parameters.Count() != 2) return false;
            return parameters[0].ParameterType == firstParamType && parameters[1].ParameterType == secondParamType;
        });
    hasConversionMethod.Invoke(propertyBuilder, new[] { firstLambdaExp, secondLambdaExp });

    这样就可以了, 其实那些 data annotation 也是这样子做出来,通过反射获取 Attribute 的值,然后调用 fluent api. 有了上面这个概念就可以写自己的 data annotation 啦~~

    反射和表达式树是 c# 常用到的功能,大家可以多学学. 

    更新 : 2018-11-26 

    这里记入一下关于 foreignKey cascade action 

    默认情况下如果我们使用 data annotation 

    required + foreginkey . ef 会帮我们设计成 cascade delete 

    如果 foreignkey + nullable 就会是 cascade restrict.

    如果使用 fluent api 的话就由我们设计了.

    ef 6.x 有一个功能可以把所有的 cascade delete 停掉, ef core 不一样了 

    https://github.com/aspnet/EntityFrameworkCore/issues/3815

    我看了一下 identity 也是有用 cascade delete 的,所以停到完好像不是 best practices.

    所以我的规范是, 要 cascade delete 的话,可以用 data annotations

    如果要限制的话,用 fluent api

    fluent api 可以双向设置.

    modelBuilder.Entity<QuestionVsSkill>().HasOne(e => e.skill).WithMany().HasForeignKey(e => e.skillId).OnDelete(DeleteBehavior.Restrict);
    modelBuilder.Entity<Skill>().HasMany<QuestionVsSkill>().WithOne(e => e.skill).HasForeignKey(e => e.skillId).OnDelete(DeleteBehavior.Restrict);

    上面 2 个是一样的结果. 

    注意 : 如果我们的 entity 有 navigation property 就要放, 如果没有就空 e.g. WithMany() <--params is empty

    有一个比较难搞的东西,就是 database 循环引用, 比如我们有继承 table, foreignkey 就不支持 sql cascade null or delete 了

    这时需要设置成 restrict 然后在处理的时候自己写程序实现 cascade null or delete. 


    Entity Framework 已经很多年了. 

    ef core 和 从前的 ef 6.x 用起来是差不多的.

    ef core 的有点事跨平台,更好的性能,持续发展中 ... 

    ef 6.x 是功能比较齐全, 但是逐渐被淘汰了.. 

    基本用法和从前是一样的, 比如 

    创建 DbContext, 定义 Entity 还有关系等等 

    namespace Project.Models
    {
        public class DB : DbContext
        {
            public DB(DbContextOptions<DB> options) : base(options)
            {
    
            }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {  
            }
            public virtual DbSet<Product> Products { get; set; }      
        }
    }

    在 startup.cs 里的 ConfigureServices 加入 

        services.AddDbContext<SchoolContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    appsetting.json 

    "ConnectionStrings": {
      "DefaultConnection": "Server=(localdb)\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
      //"DefaultConnection": "Server=192.168.1.152;Database=databaseName;User Id=username;Password=password;"
    }

    在 controller 依赖注入调用就可以了

    public class HomeController : Controller
    {
        public DB Db { get; set; }
        public HomeController(DB db) {
            Db = db;
        }
        public async Task<IActionResult> Index()
        {
            var products = await Db.Products.ToListAsync();
            return View();
        } 
    }

    要生产 database 或需要 update database 可以使用 ef core 的工具 migrations

    这个有点像是 version control 一样, 创建 migrations -> 工具会生产一个 Up 的代码和一个 Down 的代码 用于对数据库添加内容和移除内容. 

    自动生产以后,我们可以手动修改这些代码,然后 update database, 这样当我们 commit 的时候 Up 代码被执行, rollback 的时候 Down 代码被执行, 大概就是这个过程. 

    它是用命令来操作的, 打开 Package Manager Console 

    输入 add-migration init (或者 dotnet ef migrations add init)

    init 是一个名字,可以随意放. 

    输入 update-database (或者 dotnet ef database update)

    这时 Up 代码执行, 数据库就被更新了. 

    如果做错了要删除 

    输入 remove-migration (或者 dotnet ef migrations remove)

    如果要 rollback 某个 version

    输入 update-database somename 

    somename 就是你第一步我们为那一次 migrations 取得名字. 

    这样 Down 代码被执行,数据库成功还原了. 

    migrations 仅适合用于数据库架构的更新, 而不是数据库资料的更新哦. 

    多个 context 的时候,我们需要表示名字

    add-migration ids -context PersistedGrantDbContext

    或者 dotnet ef migrations add init --context applicationdbcontext

  • 相关阅读:
    20200902
    20200808
    20200801
    20191017
    LeetCode #974. Subarray Sums Divisible by K 数组
    LeetCode #532. K-diff Pairs in an Array 数组 哈希 双指针
    LeetCode #234. Palindrome Linked List 链表 栈 链表逆置
    LeetCode #307. Range Sum Query
    LeetCode #45. Jump Game II 数组 贪心
    LeetCode #55. Jump Game 数组 贪心 线性DP 回溯
  • 原文地址:https://www.cnblogs.com/keatkeat/p/9296592.html
Copyright © 2011-2022 走看看