这一小节的主要的内容是继续了解EFCore的执行过程,包括查询过滤和删除等操作。
EFCore查询过滤
一般地,查询过滤我们会这么写:
var provinces = context.Provinces
.Where(x=>x.Name=="北京");
这种Lamda表达式,相当于是:没有名称的一个方法(匿名方法)接受一个参数,即province对象。如果要替换Landa表达式,可以这么写:
public IActionResult Index()
{
var provinces = context.Provinces
.Where(FilterBeijing);
foreach (var province in provinces)
{
//Add action
}
return View();
}
private bool FilterBeijing(Province p)
{
return p.Name == "北京";
}
查询参数时,参数的设置:
1)当写死参数的时候,在执行SQL语句的时候,参数也是固定的。即Where(x=>x.Name=="北京")
对应了Select * from xxx where x.Name='Beijing'
public IActionResult Index()
{
var provinces = context.Provinces
.Where(x=>x.Name=="北京");
foreach (var province in provinces)
{
Console.WriteLine(province.Name);
//...
}
return View();
}
这样其实是有风险的,因为EFCore会认为这个参数是安全的,会原封不动的把这个参数写在SQL语句里面。
2)参数提出来,放到变量里来,然后把变量放到Linq,查看SQL语句。即var param ="北京";xxx.Where(x=>x.Name== param);
Parameters=@_param_0='Beijing'
…Where [x].[Name]=@_param_0
EFCore同样也会使用带参数的SQL语句,这样避免了很多风险。
public IActionResult Index()
{
var param = "北京";
var provinces = context.Provinces
.Where(x=>x.Name== param);
foreach (var province in provinces)
{
Console.WriteLine(province.Name);
//...
}
return View();
}
会立即执行查询的Linq方法:
- ToList() 返回一个集合
- First() 必须至少有一笔数据,返回第一笔,单笔数据。如果没有数据,则会抛异常。
- FirstOrDefault() 按照查询条件,如果有就返回第一笔;没有就返回Null,不会抛异常。
- Single() 期待我们过滤完查询之后,结果只有一笔数据。如果有多余一笔数据,或者没有数据,都会抛出异常。
- SingleOrDefault() 如果查询的结果没有一条复查查询结果,那么久返回Null;如果有一笔就返回一笔,如果有两笔的话也会抛出异常。
- Last()
- LastorDefault()
- Count()
- LongCount()
- Min()
- Max()
- Average()
会立即执行查询的非Linq方法:
- Find(主键)
FirstorDefault有两种写法,但是更推荐第二种紧凑一点的写法。
public IActionResult Index()
{
var param = "北京";
//Method 1
var province1 = context.Provinces
.Where(x => x.Name == param)
.FirstOrDefault();
//Method 2
var province2 = context.Provinces
.FirstOrDefault(x => x.Name == param);
return View();
}
根据主键来查询的两种方法如下。使用Find方法是DbSet的方法,如果这个对象已经在内存里被追踪了,那么这时候就不会去查找数据库,直接返回内存中的这个对象。
public IActionResult Index()
{
var param = "北京";
var province1 = context.Provinces
.FirstOrDefault(x => x.Id == 3);
var province2 = context.Provinces.Find(3);
return View();
}
过滤查询时会用到模糊查询,以前也许是这么写过滤条件:
public IActionResult Index()
{
var param = "北京";
var province1 = context.Provinces
.Where(x => x.Name.Contains("北"));
return View();
}
上面这个操作相当于模糊查询 Name like "%b北京%"
EFCore中有个新的方法:
public IActionResult Index()
{
var province1 = context.Provinces
.Where(x => EF.Functions.Like(x.Name, "%北%"))
.ToList();
return View();
}
当然这里还是推荐使用参数的方法来写:
public IActionResult Index()
{
string param = "%北%";
var province1 = context.Provincedds
.Where(x => EF.Functions.Like(x.Name, param))
.ToList();
return View();
}
下面看一下LastOrDefault,使用LastOrDefault首先要用Order排序;如果不排序的话就会使用内存中的顺序,这样结果是未知的。
public IActionResult Index()
{
string pa = "%北%";
var province1 = context.Provinces
.OrderBy(x=>x.Id)
.LastOrDefault(x => EF.Functions.Like(x.Name, pa));
return View();
}
EFCore修改数据
public IActionResult Index()
{
string pa = "%北%";
var province = context.Provinces.FirstOrDefault();
if (province!=null)
{
province.Population += 100;
context.SaveChanges();
}
return View();
}
这里面有两笔操作,首先查出来,然后根据Id去Update。这种情况下,context一直追踪着province这个对象。
当然,EFCore是支持批量修改的,举个例子:当context中有一个修改一个新增:
public IActionResult Index()
{
string pa = "%北%";
var province = context.Provinces.FirstOrDefault();
if (province!=null)
{
province.Population += 100;
context.Provinces.Add(new Province
{
Name = "江苏",
Population = 90000000
});
context.SaveChanges();
}
return View();
}
EFCore离线修改
首先要修改一下MyContext的生命周期。原本默认是scope,scope的生命周期是每次http请求生成一个实例。
现在要修改成Transient,Transient 是每次有需要的时候都可以创建一个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"), opts =>
{
opts.MaxBatchSize(20000);
});
options.EnableSensitiveDataLogging(true);
},ServiceLifetime.Transient);
}
在Controller中,添加另一个DbContext2,进行操作(context和context2不是同一个对象):
public class HomeController : Controller
{
private readonly MyContext context;
private readonly MyContext context2;
public HomeController(MyContext context,MyContext context2)
{
this.context = context;
this.context2 = context2;
}
public IActionResult Index()
{
var province = context.Provinces.FirstOrDefault();
context2.Provinces.Update(province);
context2.SaveChanges();
return View();
}
}
离线修改 相当于告诉context2我的province对象需要修改,除了Id其他都要改。 当然Update()也有UpdateRange()批量修改方法。
EFCore删除
EFCore删除 ,只能删除追踪的对象,即已经查出来的对象。
当然删除也是支持离线的,支持批量删除的,即有RemoveRange()方法。
public IActionResult Index()
{
var province = context.Provinces.FirstOrDefault();
context2.Provinces.Remove(province);
context2.SaveChanges();
return View();
}
原始SQl特性的支持
执行命令:DbContext.Database.ExecuteSqlCommand()
查询命令:DbSDet.FromSql()
根据Id执行存储过程
public IActionResult Index()
{
var id=5;
context.Database.ExecuteSqlCommand("exec Del……");
return View();
}