问题:

    假设有一张表,其中部分字段是经常使用的,另外还有部分占用空间大但使用频率不高的字段。由于性能原因,不需要每次都载入成本高但不经常使用的字段。这时,我们就可以将表拆分成多个实体。

解决方案:

    数据表Photograph使用IMAGE格式保存缩略图和全图。下面创建一个包含适度低成本的但频繁使用的列的实体类型和一个包含高成本但使用较少列的实体类型。

    1、生成一个派生自DbContext类的类,需要引入System.Data.Entity名称空间,添加默认构造函数,并调用父类的构造函数base("name=connectionStringName")。connectionStringName是EF框架的连接字符串名称,在配置文件中可以找到,没有的话需要手动添加或使用EDM向导的代码优先方式创建。并重写父类的OnModelCreating方法。

    2、生成一个Photograph POCO实体类和一个PhotographFullImage实体类

    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    public class Photograph
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int PhotoId { get; set; }
        public string Title { get; set; }
        public byte[] ThumbnailBits { get; set; }
        [ForeignKey("PhotoId")]
        public virtual PhotographFullImage PhotographFullImage { get; set; }
    }

    public class PhotographFullImage
    {
        [Key]
        public int PhotoId { get; set; }
        public byte[] HighResolutionBits { get; set; }
        [ForeignKey("PhotoId")]
        public virtual Photograph Photograph { get; set; }
    }

    3、在生成的派生自DbContext类的类中添加2个自动属性

        public DbSet<Photograph> Photographs { get; set; }
        public DbSet<PhotographFullImage> PhotographFullImages { get; set; }

    4、重写OnModelCreating方法

protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Photograph>()
                .HasRequired(p => p.PhotographFullImage)
                .WithRequiredPrincipal(p => p.Photograph);
            modelBuilder.Entity<Photograph>().ToTable("Photograph", "Chapter2");
            modelBuilder.Entity<PhotographFullImage>().ToTable("Photograph", "Chapter2");
        }

原理:

    EF不直接支持个别实体属性延迟载入的概念。为了获取延迟载入高成本属性的效果,我们拓展了EF对关联实体延迟载入的支持。我们生成了一个新的实体用于保存高成本的full p_w_picpath属性,并在Photograph实体类型和新的PhototgraphFullImage实体类型间生成一个一对一的关系。在概念层添加了一个类似于数据库参照约束的参照约束,告诉EF只有存在了Photograph,PhotographFullImage才能存在。

    由于参照引用,在我们的模型中有一些事情需要被注解。如果我们有一个新近生成的PhotographFullImage实例,在调用SaveChanges()方法前,一个Photograph实例必须在上下文中或者在数据源中。再者,如果我们删除一个photograph对象,它相关的PhotographFullImage实例也将被删除。这个有点像数据库参照引用的级联删除。

            using (var context = new EF6Recipes7Context())
            {
                byte[] thumbBits = new byte[100];
                byte[] fullBits = new byte[2000];
                var photo = new Photograph
                {
                    Title = "My Dog",
                    ThumbnailBits = thumbBits
                };

                var fullImage = new PhotographFullImage { HighResolutionBits = fullBits };
                photo.PhotographFullImage = fullImage;
                context.Photographs.Add(photo);
                context.SaveChanges();
            }

            using (var context = new EF6Recipes7Context())
            {
                foreach (var photo in context.Photographs)
                {
                    Console.WriteLine("Photo: {0}, ThumbnailSize {1} bytes",
                        photo.Title, photo.ThumbnailBits.Length);
                    context.Entry(photo).Reference(p => p.PhotographFullImage).Load();
                    Console.WriteLine("Full Image Size: {0} bytes.", photo.PhotographFullImage.HighResolutionBits.Length);
                }
            }

    为了不必每次都清空数据库,可以在Main方法开头处添加如下代码

Database.SetInitializer(new DropCreateDatabaseAlways<EF6Recipes7Context>());

    这样每次启动程序时都将删除并重建数据库。

    上面的代码首先生成并实例化了Photograph和PhotographFullImage实体,并将它们添加到Context中,然后调用SaveChanges方法保存到数据库中。

    在查询部分,我们从数据库中获取每一个photograph,并打印它的相关信息,然后显式载入相应的PhotographFullImage实体。注意:我们并没有改变默认的context选项关闭延迟载入,而是显式载入相关实体。