您现在的位置是: 首页 >  ASP.NET教程 >  文章详情 文章详情

持久化:EF-4:关联增删改查

2020-05-09 【ASP.NET教程】 1122人浏览

通过Linq查询,我们能获得entity本身,但entity关联(related)的数据呢?比如,Blog.Posts……


关联数据存储

(复习:将一个entity添加到DbSet,会将它关联的全部实体添加到context中)

同样的,context中被跟踪的entity,它的关联数据也会被存储

    var blog = context.Blogs
        .Include(b => b.Posts)       //注意必须使用Include
        .First();
    var post = new Post { Title = "Intro to EF Core" };

    blog.Posts.Add(post);
    context.SaveChanges();


加载(load)模式


EF默认不会加载entity所关联的entity(navigation properties)

     return entities.Where(s => s.Name == name).SingleOrDefault();

EF提供了三种加载关联数据的模式

  • 预先(Eager)加载:关联的数据会立即,亦即在query表达式被执行时被加载

                var result = entities                    //实际上是Student
                    .Where(s => s.Name == name)
                        .Include(s => s.Teacher)         //包含entites中的Teacher
                            .ThenInclude(t=>t.TeachOn)        //包含Teacher中的TeachOn
                        .Include(s => s.Partern)           //.Select(s=>s.Name)                 //只取Student的Name
                ;

    取单个(Property):

    取集合(Collection): ThenInclude()的智能提示可能有问题,略过直接运行

    注意:
    Include中可以强制转换(多态):

          .ThenInclude(t => ((Teacher)t).TeachOn)      .ThenInclude(t => (t as Teacher).TeachOn)
  1. 一旦被加载,就会被(一级)缓存/跟踪

  2. 如果关联数据此前已经被缓存,那么即使你没有Include,一样可以直接使用

  3. 如果最后的结果(Selcet语句)没有使用到Include()里的值,Include会被ignore

  • 显式(Explicit)加载:稍后(即需要用到的时候)从数据库加载,但关联数据不能预先加载

         return entities.Where(s => s.Name == name).SingleOrDefault();

    要Blog.Author有值,需要:

                studentRep.Entry(ht)    //可以操作ht的ChangeTracker
                    .Reference(s => s.PersonOfTeacher)  //显式的获取ht的PersonOfTeacher
                    .Load();    //调用Load(),加载PersonOfTeacher,将其“融入”ht

    补充/复习:影子属性,数据库中有但entity中不体现的属性,也可以通过Entry()读写

                studentRep.Entry(ht).Property("ShadowTime").CurrentValue = DateTime.Now;
    如果是关联的集合,注意使用Query()避免全部加载:

                studentRep.Entry(ht)                .Collection(m => m.Majors)                    .Query().Where(m => m.MajorId > 3)                .Load();


  • 延迟(惰性Lazy)加载:
    在.NET core 2.1之后支持,需要添加引用Microsoft.EntityFrameworkCore.Proxies,并显式开启:

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

    而且必须添加virtual关键字,且class能够被继承

    public virtual ICollection Posts { get; set; }

    背后原理:proxy模式

优劣总结:

  • Lazy Load:使用起来最方便,但缺乏精细化控制(至少目前是这样的?)有可能带来1+n的性能问题

  • Explicit Load:会产生多次查询,但可以使用Query()对关联数据进行SQL语句筛选,而且可以重用

  • Eager Load:使用join查询,减少数据库访问次数,但不适合关联大数据的情形(尤其是我们实际上只需要部分关联数据的时候)

飞哥个人偏好:Lazy为主,Eager为附


为什么需要?

而不使用用query查询(Join):保证entity不依赖于repository(database)


级联(Cascade)删除


举例说明:删除一个学生,他的成绩/专业/老师怎么办?

  • 留着:成绩所属学生已经没有了,但其引用(外键)关系

  1. 也还留着:违反外键约束,或者一旦引用就报异常

  2. 设置为null:就会成为“垃圾”数据

  • 一起删除?干净利索,^_^


  • 出现entity之间的关联时:

    • 在删除子entity,不会影响父enity;

    • 删除父entity的时候,可以同时删除(或处理)子entity,但其

    前提是:

    • 子entity要已经加入DbContext (比如1:n的时候,n要双向引用且被加载到DbContext)

    • 数据库要有相应的Cascade设置。如果通过Migration或EnsureCreated创建的数据库,数据库上的Cascade会自动配置……

    确实需要删除的时候,EF core为其(加载到context中的)子entity提供了三种可选择方案:

    1. 被同时删除

    2. 其外键被设置为null (如果外键不能被设置为null,会报异常)

    3. 不发生改变

    具体来说,可设置DeleteBehavior枚举(常用的三种),指定EF core如何操作。

                modelBuilder.Entity()
                    .HasMany(b => b.Posts)
                    .WithOne(p => p.Blog)
                    .OnDelete(DeleteBehavior.Cascade);

    EF根据子entity的外键是否可以为Null,默认设置Behavior处理:

    • 如果外键可以为空:DeleteBehavior.ClientSetNull,子entity的外键设为Null

    • 如果外键不可以为空:DeleteBehavior.Cascade,子entity同时被删除

    如果说EF的Cascade配置和数据库设置相冲突,会报异常。


    ------------------

    当entity之间的关系复杂之后,级联删除会导致复杂的链式反应……

    所以我们通常都不进行物理删除,而是用flag标记删除(在entity中添加一个是否已删除的Flag列),最后再由DBA批量删除,其优势为:

    • 减少用户等待时间

    • 可以回溯撤销

    • 便于控制意外情况发生

    ------------------


    自定义SQL操作

    当Linq语句无法(由provider)自动生成/translate,或者自动生成的SQL语法无法满足开发要求的时候,我们可以使用自定义的原生SQL语句:

                Student student = entities
                    //使用内插字符串会自动参数化                .FromSqlInterpolated($"SELECT * FROM Person WHERE [Name]={name}")                //或者传递参数                .FromSqlRaw("SELECT * FROM Person WHERE [Name]= @name",                     new SqlParameter("name", name))    //"name" 前面不加@                .SingleOrDefault();

    (复习:参数化查询

    使用SqlParameter时注意其名称空间:

    usingMicrosoft.Data.SqlClient;  //对//using System.Data.SqlClient; 错!

    其实最常见的应用场景:老项目翻新。


    延伸:避免循环引用造成的Json序列化错误:

        services.AddMvc()
            .AddJsonOptions(
                options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
            );

    或者在property上添加[JsonIgnore]



    很赞哦! (0)

    站长推荐