ASP.NET Core 中的 ORM 之 Entity Framework (4)

警告询问用户:当一个用户尝试更新一个记录时,但是该记录自从他读取之后已经被别人修改了,这时应用程序就会警告该用户该数据已经被某人更改了,然后询问他是否仍然要重写该数据还是首先检查已经更新的数据。

执行 SQL 语句和存储过程

EF Core 使用以下方法执行 SQL 语句和存储过程:

DbSet

DbSet<TEntity>.FromSql() 返回值为IQueryable,可以与Linq扩展方法配合使用。注意:

SQL 查询必须返回实体或查询类型的所有属性的数据

结果集中的列名必须与属性映射到的列名称匹配。

SQL 查询不能包含相关数据。 但是可以使用 Include 运算符返回相关数据。

不要使用 TOP 100 PERCENT 或 ORDER BY 等子句。可以通过 Linq 在代码里面编写。

基本 SQL 查询

var blogs = _context.Blogs.FromSql($"select * from Blogs").ToList();

带有参数的查询:

var blog = _context.Blogs.FromSql($"select * from Blogs where BlogId = {id}");

使用 LINQ:

var blogs = _context.Blogs.FromSql($"select * from Blogs") .OrderByDescending(r => r.Rating) .Take(2) .ToList();

通过 SQL Server Profiler 查看 SQL 语句,可以发现 EF Core 是把手工写的 SQL 语句和 Linq 合并生成了一条语句:

exec sp_executesql N'SELECT TOP(@__p_1) [r].[BlogId], [r].[Rating], [r].[Timestamp], [r].[Url] FROM ( select * from Blogs ) AS [r] ORDER BY [r].[Rating] DESC',N'@__p_1 int',@__p_1=2

使用 Include 包括相关数据

var blogs = _context.Blogs.FromSql($"select * from Blogs").Include(r => r.Posts).ToList();

通过 SQL Server Profiler 查看 SQL 语句:

SELECT [b].[BlogId], [b].[Rating], [b].[Timestamp], [b].[Url] FROM ( select * from Blogs ) AS [b] ORDER BY [b].[BlogId] SELECT [b.Posts].[PostId], [b.Posts].[BlogId], [b.Posts].[Content], [b.Posts].[Title] FROM [Posts] AS [b.Posts] INNER JOIN ( SELECT [b0].[BlogId] FROM ( select * from Blogs ) AS [b0] ) AS [t] ON [b.Posts].[BlogId] = [t].[BlogId] ORDER BY [t].[BlogId]

DbContext.Database.ExecuteSqlCommand()

ExecuteSqlCommand方法返回一个整数,表示执行的SQL语句影响的行数。有效的操作是 INSERT、UPDATE 和 DELETE,不能用于返回实体。

测试一下 INSERT:

int affectRows = _context.Database.ExecuteSqlCommand($"Insert into Blogs([Url],[Rating])Values({blog.Url}, {blog.Rating})");

通过 SQL Server Profiler 查看 SQL 语句:

exec sp_executesql N'Insert into Blogs([Url],[Rating])Values(@p0, @p1)',N'@p0 nvarchar(4000),@p1 int',@p0=N'testurl',@p1=3

延迟加载和预先加载

EF Core 通过在模型中使用导航属性来加载相关实体。 有三种常见模式可用于加载相关数据。

预先加载
表示从数据库中加载相关数据,作为初始查询的一部分。使用 Include方法实现预加载,使用 ThenInclude 实现多级预加载。

var blogs = _context.Blogs.Include(r => r.Posts).ToList();

当需要 JSON 序列化 blogs 对象时候,ASP.NET Core 自带的序列化库 Newtonsoft.Json 可能会抛出自引用循环异常。请在 Startup 的 ConfigureServices 方法中配置以下代码解决。

public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); }

显式加载
表示稍后从数据库中显式加载相关数据。

var blog = await _context.Blogs.FindAsync(id); _context.Entry(blog) .Collection(b => b.Posts) .Load();

延迟加载
表示在访问导航属性时,才从数据库中加载相关数据。在 EF Core 2.1 中才引入此功能。

Nuget 安装 Microsoft.EntityFrameworkCore.Proxies

调用 UseLazyLoadingProxies 来启用延迟加载。

services.AddDbContext<BloggingContext>(option => option.UseLazyLoadingProxies().UseSqlServer(connectionString));

导航属性添加 virtual 修饰符。

public class Blog { public int BlogId { get; set; } public string Url { get; set; } public int Rating { get; set; } public virtual IList<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public virtual Blog Blog { get; set; } }

测试,当代码执行到var posts = blog.Posts时候,会去数据库里面查询Posts记录。

var blog = await _context.Blogs.FindAsync(id); var posts = blog.Posts;

尽量避免在循环时候使用延迟加载,会导致每次循环都去访问数据库。

IQueryable 和 IEnumerable

直接通过一个实例测试一下:

var testIQueryable = _context.Blogs.Where(r => r.Rating > 10); var testIEnumerable = _context.Blogs.AsEnumerable().Where(r => r.Rating > 10); var testIQueryableList = testIQueryable.ToList(); var testIEnumerableList = testIEnumerable.ToList();

查看生产的 SQL 语句

IQueryable

SELECT [r].[BlogId], [r].[Rating], [r].[Timestamp], [r].[Url] FROM [Blogs] AS [r] WHERE [r].[Rating] > 10

IEnumerable

SELECT [b].[BlogId], [b].[Rating], [b].[Timestamp], [b].[Url] FROM [Blogs] AS [b]

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgffdz.html