这一小节主要的内容是通过观察EFCore的执行日志了解EFCore的执行过程。
将EFCore的SQL执行过程输出到Log
输出到Console:
ASPNetCore的项目已经集成好了Log工具,可以通过查看Program.cs中的CreateDefaultBuilder来查看。
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup();
}
ASPNetCore配置了Log工具,可以输出到Debug窗口和控制台。所以ASP DotNet Core项目就不需要配置Log工具了。
如果不使用ASP DotNet Core项目,可以参考文档:https://docs.microsoft.com/zh-cn/ef/core/miscellaneous/logging
EFCore新增数据
增加province这个Model,当New Province的时候需要注意:Id 是作为主键。
这里需要知道这样一个约定:如果有Id或者ProvinceId,这时它是默认作为主键的,如果使用int的话,是自增的,那么新增的时候也就不用给Id赋值了。
public class Province
{
public Province()
{
Cities = new List();
}
public int Id { get; set; }
public string Name { get; set; }
public int Population { get; set; }
public List Cities { get; set; }
}
正常情况我们可能是这么写,让context自动dispose:
var province = new Province()
{
Name = "北京",
Population = 20000
};
using (var context=new MyContext())
{
//Add province
}
但是在ASP DotNetCore项目中,已经把MyContext添加到容器中了。在Startup.cs中:
public void ConfigureServices(IServiceCollection services)
{
services.Configure(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
}
因此我们可以在Controller的构造函数中进行注入:
public class HomeController : Controller
{
private readonly MyContext context;
public HomeController(MyContext context)
{
this.context = context;
}
}
然后直接用,不需要手动dispose,因为清理的工作由容器自动完成。添加的写法有以下两种:
public class HomeController : Controller
{
private readonly MyContext context;
public HomeController(MyContext context)
{
this.context = context;
}
public IActionResult Index()
{
var province = new Province()
{
Name = "北京",
Population = 20000
};
//Method 1(recommend)
context.Provinces.Add(province);
//context is now Tracking procince object
//Method 2:
context.Add(province);
return View();
}
}
到这里,province只是在内存里并且被context追踪,但是并没有插入到数据库。要执行插入动作,还需要:context.SaveChanges();
context.Province.Add(province)
:
MyCOntext开始追踪province对象Context.SaveChanges()
:
检查所有MyContext正在追踪的对象
读取每个对象的状态
生成SQL语句
执行所有生成的SQL语句
如果有返回数据的话,就获取这些返回数据
下面我们来打印一下SQL语句,注意修改启动方式,不使用IIS,使用控制台。
注意一下,Savechange()方法很有可能执行批量操作,但凡其中有一步出现错误,都会进行回滚。可以观察一下在执行过程中province的变化:
传入参数Name Population在日志中是加密敏感数据的,但是在开发的时候我们也许会想看一下这些传入的参数。
因此,可以在依赖注入的时候配置参数options参数,允许敏感数据记录EnableSensitiveDataLogging。
public void ConfigureServices(IServiceCollection services)
{
services.Configure(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging(true);
});
}
这个时候我们再观察一下日志记录的结果,可以看到输入的参数:北京 20000
EFCore批量添加数据
批量添加,有两种方法,如下:
public IActionResult Index()
{
var province1 = new Province()
{
Name = "上海",
Population = 700000
};
var province2 = new Province()
{
Name = "天津",
Population = 300000
};
var province3 = new Province()
{
Name = "广东",
Population = 900000
};
//Method 1
//context.Provinces.AddRange(province1, province2, province3);
//Method 2
context.Provinces.AddRange(new List
{
province1,province2,province1
});
context.SaveChanges();
return View();
}
通过观察日志,这两种方法的效果是一样的,都是3个Insert。
下面批量添加没有关系的Company和Province的两个对象,直接用context.AddRange()这个方法。
public IActionResult Index()
{
var province = new Province()
{
Name = "山东",
Population = 700000
};
var company = new Company()
{
Name = "Taida",
EstablishDate = new DateTime(1990,1,1),
LegalPerson = "Secret Man"
};
context.AddRange(province,company);
context.SaveChanges();
return View();
}
通过观察日志,EFCore分别执行了两次SQL操作。
批量操作的大小限制:
默认大小限制是由数据库Provider定的,SQLServer是1000个命令。
如果超出该大小限制,那么超出的部分将会做了另外的批次来执行。
在Startup.cs中的ConfigureServices中改下注入的配置:
services.AddDbContext(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), opts =>
{
opts.MaxBatchSize(20000);
});
options.EnableSensitiveDataLogging(true);
});
EFCore 查询
前面都是组SQL语句,只有当遇到ToList()的时候才会真正执行查询。推荐第一种。
public IActionResult Index()
{
var provinces = context.Provinces.ToList();
//Method 1 Filter by province name
var provinces1 = context.Provinces
.Where(x=>x.Name=="北京")
.ToList();
//Method 2 Linq to SQL
var provinces2 = (from p in context.Provinces
where p.Name == "北京"
select p).ToList();
return View();
}
我们可以去掉ToList(),发现是IQueryable类型。
遇到foreach也是查询数据库的操作。
public IActionResult Index()
{
var provinces = context.Provinces
.Where(x=>x.Name=="北京");
foreach (var province in provinces)
{
//Add action
}
return View();
}
对于这种情况,如果foreach里面的操作耗时较长,那么不推荐这么做。因为长时间打开数据库连接,会有风险,例如其他人正在用这个表。
所以还是推荐用tolist.