• 03-EF Core笔记之查询数据


    EF Core使用Linq进行数据查询。

    基本查询

    微软提供了一百多个示例来演示查询,地址:https://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b

    我们可以通过下面的代码进行简单的查询:

    //获取全部数据
    var blogs = context.Blogs.ToList();
    
    //获取单个实体
    var blog = context.Blogs.Single(b => b.BlogId == 1);
    
    //筛选
    var blogs = context.Blogs
        .Where(b => b.Url.Contains("dotnet"))
        .ToList();
    

    加载关联数据

    EF Core有三种常见模型来加载关联数据:

    • 预先加载:表示从数据库中加载关联数据,作为初始查询的一部分
    • 显式加载:表示稍后从数据库中显式加载关联数据
    • 延迟加载:表示在访问关联数据时,再从数据库中加载关联数据

    预先加载

    使用Include方法指定要包含在查询结果中的关联数据。例如:

    using (var context = new BloggingContext())
    {
        var blogs = context.Blogs
            .Include(blog => blog.Posts)
            .Include(blog => blog.Owner)
            .ToList();
    }
    

    关联数据可以是有层级的,可通过链式调用ThenInclude,进一步包含更深级别的关联数据。:

    using (var context = new BloggingContext())
    {
        var blogs = context.Blogs
            .Include(blog => blog.Posts)
                .ThenInclude(post => post.Author)
                    .ThenInclude(author => author.Photo)
            .Include(blog => blog.Owner)
                .ThenInclude(owner => owner.Photo)
            .ToList();
    }
    

    如果更改查询,从而使其不再返回查询以之为开头的实体类型的实例,则会忽略 include 运算符。例如:

    using (var context = new BloggingContext())
    {
        var blogs = context.Blogs
            .Include(blog => blog.Posts)
            .Select(blog => new
            {
                Id = blog.BlogId,
                Url = blog.Url
            })
            .ToList();
    }
    

    此时EF Core会忽略包含,并生成警告日志。

    显式加载

    通过 DbContext.Entry(...) API 显式加载导航属性。例如:

    using (var context = new BloggingContext())
    {
        var blog = context.Blogs
            .Single(b => b.BlogId == 1);
    
        context.Entry(blog)
            .Collection(b => b.Posts)
            .Load();
    
        context.Entry(blog)
            .Reference(b => b.Owner)
            .Load();
    }
    

    延迟加载

    使用延迟加载的最简单方式是通过安装 Microsoft.EntityFrameworkCore.Proxies 包,并通过调用 UseLazyLoadingProxies 来启用该包。 例如:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseLazyLoadingProxies()
            .UseSqlServer(myConnectionString);
    

    或者在ServiceConfigure中,调用services.AddDbContext方法时启用:

    services.AddDbContext<BloggingContext>(
        b => b.UseLazyLoadingProxies()
              .UseSqlServer(myConnectionString));
    

    EF Core 延迟加载需要属性必须具有是共有的,且具有virtual修饰符,只有这样才可以被子类重写。为何要这样做,可以参考我之前的文章《Castle DynamicProxy基本用法(AOP)》。

    下面的代码演示了延迟加载的用法:

    public class Blog
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        public virtual ICollection<Post> Posts { get; set; }
    }
    
    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
    
        public virtual Blog Blog { get; set; }
    }
    

    此时EF Core会使用代理类进行延迟加载数据。

    EF Core还提供了不使用代理的方式进行延迟加载,此方法需要向实体类中注入ILazyLoader实例,并通过该实例实现get访问:

    public class Blog
    {
        private ICollection<Post> _posts;
    
        public Blog()
        {
        }
    
        private Blog(ILazyLoader lazyLoader)
        {
            LazyLoader = lazyLoader;
        }
    
        private ILazyLoader LazyLoader { get; set; }
    
        public int Id { get; set; }
        public string Name { get; set; }
    
        public ICollection<Post> Posts
        {
            get => LazyLoader.Load(this, ref _posts);
            set => _posts = value;
        }
    }
    

    此种方法需要注入ILazyLoader,从而造成更多的包依赖。

    使用EF Core延迟加载,可能会造成循环引用,此时无法使用Json.Net进行序列化,需要对此进行一些配置:

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

    客户端 vs. 服务器

    EF Core支持部分查询在客户端进行、部分查询发送到服务器,此种情况下可能会造成性能问题。

    当发生客户端筛选数据的时候,EF Core会发出警告,也可以配置当发生客户端筛选时抛出异常:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(@"Server=(localdb)mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
            .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
    }
    

    跟踪和非跟踪

    默认情况下,EF Core跟踪查询返回的实体,如果我们不需要跟踪查询返回的实体,则可以通过AsNoTracking方法禁用跟踪。

    using (var context = new BloggingContext())
    {
        var blogs = context.Blogs
            .AsNoTracking()
            .ToList();
    }
    

    或者在DbContext级别禁用跟踪:

    using (var context = new BloggingContext())
    {
        context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    
        var blogs = context.Blogs.ToList();
    }
    

    当使用投影查询结果时,如果包含实体类型,则会对实体类型执行跟踪,例如下面的查询,将会对Blog和Post进行跟踪:

    using (var context = new BloggingContext())
    {
        var blog = context.Blogs
            .Select(b =>
                new
                {
                    Blog = b,
                    Posts = b.Posts.Count()
                });
    }
    

    另外,如果查询结果中不包含任何实体类型,则不执行跟踪。例如:

    using (var context = new BloggingContext())
    {
        var blog = context.Blogs
            .Select(b =>
                new
                {
                    Id = b.BlogId,
                    Url = b.Url
                });
    }
    

    原始SQL查询

    当Linq无法满足查询需求,或因为使用Linq生成效率比较低的SQL查询时,可以考虑使用原始SQL进行查询。EF Core支持原始SQL语句和存储过程。

    原始SQL语句:

    var blogs = context.Blogs
        .FromSql("SELECT * FROM dbo.Blogs")
        .ToList();
    

    存储过程:

    var blogs = context.Blogs
        .FromSql("EXECUTE dbo.GetMostPopularBlogs")
        .ToList();
    

    参数传递

    当使用原始SQL进行查询时,必须使用参数化查询以抵御SQL注入攻击。

    好的一点是,EF Core在设计时就替我们考虑了如何防御SQL注入攻击,因此当我们使用FromSql方法时,参数中如果有使用到拼接字符串的情况,则会自动为我们生成SQL查询参数,例如:

    var user = "johndoe";
    
    var blogs = context.Blogs
        .FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
        .ToList();
    

    上面的SQL语句虽然看上去像是直接拼接的字符串,其实EF Core已经为我们生成了查询参数。

    当然了,我们也可以手工创建查询参数:

    var user = new SqlParameter("user", "johndoe");
    
    var blogs = context.Blogs
        .FromSql("EXECUTE dbo.GetMostPopularBlogsForUser @user", user)
        .ToList();
    

    当数据库的存储过程使用了命名参数时,手工创建查询参数将会派上用场:

    var user = new SqlParameter("user", "johndoe");
    
    var blogs = context.Blogs
        .FromSql("EXECUTE dbo.GetMostPopularBlogs @filterByUser=@user", user)
        .ToList();
    

    拼接Linq

    当我们使用原始SQL查询时,EF Core仍然支持我们使用linq编写查询语句。在执行查询时,EF Core会检查我们的sql语句是否支持拼接,如果支持的情况下,则会将linq过滤语句拼接为sql一并发送到数据库进行查询。

    跟踪

    原始SQL中的跟踪与Linq查询的跟踪方式一致。

    关联数据

    原始SQL中查询关联数据的方式与Linq查询的关联方式一致。

    全局筛选器

    全局筛选器对于软删除和多租户非常有用。定义方式如下:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");
    
        // Configure entity filters
        modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
        modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
    }
    

    我们可以在特定的查询中禁用全局筛选器:

    blogs = db.Blogs
        .Include(b => b.Posts)
        .IgnoreQueryFilters()
        .ToList();
    
  • 相关阅读:
    正则匹配任意字(包括换行符)
    linux终端光标的快捷键操作
    正则向前查找和向后查找
    正则表达式软件Expresso
    JsonP 跨域完全解析
    PHP代码阅读Phpxref
    ubuntu 操作用户名和密码
    curl多线程解释[转]
    php递归创建多级目录
    离散数学 第一章 命题逻辑 11 命题及其表示法
  • 原文地址:https://www.cnblogs.com/youring2/p/11186614.html
Copyright © 2020-2023  润新知