Asp.net core 学习笔记 ( ef core )

更新: 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

 

转载于:https://www.cnblogs.com/keatkeat/p/9296592.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值