Hello,开始回归开始每周更新一到两篇博客,本节我们回归下EF Core基础,来讲述EF Core中到底是如何映射的,废话少说,我们开始。

One-Many Relationship(一对多关系)

首先我们从最简单的一对多关系说起,我们给出需要映射的两个类,一个是Blog,另外一个则是Post,如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class Blog
    {        public int Id { get; set; }        public int Count { get; set; }        public string Name { get; set; }        public string Url { get; set; }        public IEnumerable<Post> Posts { get; set; }

    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class Post
    {        public virtual int Id { get; set; }        public virtual string Title { get; set; }        public virtual string Content { get; set; }        public virtual int BlogId { get; set; }        public virtual Blog Blog { get; set; }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

此时我们从Blog来看,一个Blog下对应多个Post,而一个Post对应只属于一个Blog,此时配置关系如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

     public class BlogMap : EntityMappingConfiguration<Blog>
    {        public override void Map(EntityTypeBuilder<Blog> b)
        {
            b.ToTable("Blog");
            b.HasKey(k => k.Id);

            b.Property(p => p.Count);
            b.Property(p => p.Url);
            b.Property(p => p.Name);            b.HasMany(p => p.Posts)
                .WithOne(p => p.Blog)
                .HasForeignKey(p => p.BlogId);
        }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

而Post则为如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class PostMap : EntityMappingConfiguration<Post>
    {        public override void Map(EntityTypeBuilder<Post> b)
        {
            b.ToTable("Post");
            b.HasKey(k => k.Id);
            b.Property(p => p.Title);
            b.Property(p => p.Content);
        }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

此时我们利用SqlProfiler监控生成的SQL语句。如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

CREATE TABLE [Blog] (
    [Id] int NOT NULL IDENTITY,
    [Count] int NOT NULL,
    [Name] nvarchar(max),
    [Url] nvarchar(max),
    CONSTRAINT [PK_Blog] PRIMARY KEY ([Id])
);

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

CREATE TABLE [Post] (
    [Id] int NOT NULL IDENTITY,
    [BlogId] int NOT NULL,
    [Content] nvarchar(max),
    [Title] nvarchar(max),
    CONSTRAINT [PK_Post] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Post_Blog_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blog] ([Id]) ON DELETE CASCADE
);

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

此时我们能够很明确的看到对于Post表上的BlogId建立外键BlogId,也就是对应的Blog表上的主键即Id,同时后面给出了DELETE CASADE即进行级联删除的标识,也就是说当删除了Blog上的数据,那么此时Post表上对应的数据也会进行相应的删除。同时在生成SQL语句时,还对Post上的BlogId创建了索引,如下:

CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);

由上知,对于一对多关系中的外键,EF Core会默认创建其索引,当然这里的索引肯定是非唯一非聚集索引,聚集索引为其主键。我们通过数据库上就可以看到,如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

此时即使我们不配置指定外键为BlogId同样也没毛病,如下:

b.HasMany(m => m.Posts).WithOne(o => o.Blog);

因为上述我们已经明确写出了BlogId,但是EF Core依然可以为其指定BlogId为外键,现在我们反过来想,要是我们将Post中的BlogId删除,同样进行上述映射是否好使呢,经过实际验证确实是可以的,如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

别着急下结论,我们再来看一种情况,现在我们进行如下配置并除去Post中的BlogId还是否依然好使呢?

b.HasMany(m => m.Posts);

经过临床认证,也是好使的,能够正确表达我们想要的效果并自动添加了外键BlogId列,所以到这里我们可以为一对多关系下个结论:

一对多关系结论

在一对多关系中,我们可以通过映射明确指定外键列,也可以不指定,因为EF Core内部会查找是否已经指定其外键列有则直接用指定的,没有则自动生成一个外键列,列名为外键列所在的类名+Id。同时对于一对多关系我们可以直接只使用HasMany方法来配置映射而不需要再配置HasOne或者WithOne,上述皆是从正向角度去配置映射,因为易于理解,当然反之亦然。

One-One RelationShip (一对一关系)

对于一对一关系和多对多关系稍微复杂一点,我们来各个击破,我们通过举例比如一个产品只属于一个分类,而一个分类下只有一个产品,如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class Product
    {        public int Id { get; set; }        public string Name { get; set; }        public Category Category { get; set; }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

       Id { ;   Name { ;  Product Product { ;

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

此时我们来进行一下一对一关系映射从产品角度出发:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

      ProductMap : EntityMappingConfiguration<Product>   Map(EntityTypeBuilder<Product>=>=> o.Category).WithOne(o =>

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

此时我们通过 dotnet ef migrations add Initial 初始化就已经出现如下错误:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

大概意思为未明确Product和Category谁是依赖项,未明确指定导致出现上述错误。而上述对于一对多关系则不会出现如此错误,仔细分析不难发现一对多已经明确谁是主体,而对于一对一关系二者为一一对应关系,所以EF Core无法判断其主体,所以必须我们手动去指定。此时我们若进行如下指定你会发现没有lambda表达式提示:

 b.HasOne(o => o.Category)
                .WithOne(o => o.Product)
                .HasForeignKey(k=>k.)

还是因为主体关系的原因,我们还是必须指定泛型参数才可以。如下所示:

 b.HasOne(o => o.Category)
                .WithOne(o => o.Product)
                .HasForeignKey<Category>(k => k.ProductId);

此时在Category上创建ProductId外键,同时会对ProductId创建如下的唯一非聚集索引:

CREATE UNIQUE INDEX [IX_Category_ProductId] ON [Category] ([ProductId]);

Many-Many RelationShip (多对多关系)

多对多关系在EF Core之前版本有直接使用的方法如HasMany-WithMany,但是在EF Core中则不再提供对应的方法,想想多对多关系还是可以通过一对多可以得到,比如一个产品属于多个分类,而一个分类对应多个产品,典型的多对多关系,但是通过我们的描述则完全可以通过一对多关系而映射得到,下面我们一起来看看:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class Product
    {        public int Id { get; set; }        public string Name { get; set; }        public IEnumerable<ProductCategory> ProductCategorys { get; set; }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class Category
    {        public int Id { get; set; }        public string Name { get; set; }        public int ProductId { get; set; }        public IEnumerable<ProductCategory> ProductCategorys { get; set; }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class ProductCategory
    {        public int ProductId { get; set; }        public Product Product { get; set; }        public int CategoryId { get; set; }        public Category Category { get; set; }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

上述我们将给出第三个关联类即ProductCategory,将Product(产品类)和Category(分类类)关联到ProductCategory类,最终我们通过ProductCategory来进行映射,如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class ProductCategoryMap : EntityMappingConfiguration<ProductCategory>
    {        public override void Map(EntityTypeBuilder<ProductCategory> b)
        {
            b.ToTable("ProductCategory");

            b.HasKey(k => k.Id);

            b.HasOne(p => p.Product)
                .WithMany(p => p.ProductCategorys)
                .HasForeignKey(k => k.ProductId);

            b.HasOne(p => p.Category)
                .WithMany(p => p.ProductCategorys)
                .HasForeignKey(k => k.CategoryId);
        }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

好了到了这里为止,关于三种映射关系我们介绍完了,是不是就此结束了,远远不是,下面我们再来其他属性映射。

键映射

关于键映射中的外键映射上述已经讨论过,下面我们来讲讲其他类型键的映射。

备用键/可选键映射(HasAlternateKey)

备用键/可选键可以为一个实体类配置除主键之外的唯一标识,比如在登录中用户名可以作为用户的唯一标识除了主键标识外,这个时候我们可以为UserName配置可选键,打个比方这样一个场景:一个用户只能购买一本书,在Book表中配置一个主键和用户Id(例子虽然不太恰当却能很好描述可选键的使用场景)

    public class Book
    {        public int Id { get; set; }        public string UserId { get; set; }
    }

下面我们通过可选键来配置用户Id的映射

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class BookMap : EntityMappingConfiguration<Book>
    {        public override void Map(EntityTypeBuilder<Book> b)
        {
            b.ToTable("Book");
            b.HasKey(k => k.Id);
            b.HasAlternateKey(k => k.UserId);
        }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

最后监控得到如下语句:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

看到没,为用户Id配置了唯一约束:

CONSTRAINT [AK_Book_UserId] UNIQUE ([UserId])

所以我们得出结论:通过可选键我们可以创建唯一约束来除主键之外唯一标识行。

主体键映射(Principal Key) 

如果我们想要一个外键引用一个属性而不是主键,此时我们可以通过主体键映射来进行配置,此时配置主体键映射背后实际上自动将其设置为一个可选键。这个就不用我们多讲了。

好了到此为止我们讲完了键映射,接下来我们再来讲述属性映射:

属性映射

对于C#中string类型若我们不进行配置,那么在数据库中将默认设置为NVARCHAR并且长度为MAX且是为可空,如下:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

若我们需要设置其长度且为非空,此时需要进行如下配置:

b.Property(p => p.Name).IsRequired().HasMaxLength(50);

通过HaxMaxLength方法来指定最大长度,通过IsRequired方法来指定为非空。但是此时问题来了,数据库类型对于string有VARCHAR、CHAR、NCAHR类型,那么我们应当如何映射呢?比如对于VARCHAR类型,在EF Core中对于数据库列类型我们可以通过 HasColumnType 方法来进行映射,那么假设对于数据库类型为VARCHAR长度为50且为非空,我们是否可以进行如下映射呢?

         b.Property(p => p.Name)
                .IsRequired()
                .HasColumnType("VARCHAR")
                .HasMaxLength(50);

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

通过上述迁移出错,我们修改成如下才正确:

b.Property(p => p.Name)
                .IsRequired()                .HasColumnType("VARCHAR(50)");

解决一个,又来一个,那么对于枚举类型我们又该进行如何映射呢,枚举对应数据库中的类型为TINYINT,我们进行如下设置:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class Product
    {        public int Id { get; set; }        public string Name { get; set; }        public Type Type { get; set; }        public IEnumerable<ProductCategory> ProductCategorys { get; set; }
    }    public enum Type
    {
        [Description("普通")]
        General = 0,
        [Description("保险")]
        Insurance = 1
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public class ProductMap : EntityMappingConfiguration<Product>
    {        public override void Map(EntityTypeBuilder<Product> b)
        {
            b.ToTable("Product");
            b.HasKey(k => k.Id);            b.Property(p => p.Type)
                .IsRequired()
                .HasColumnType("TINYINT");
        }
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

此时则对应生成我们想要的类型:

CREATE TABLE [Product] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max),
    [Type] TINYINT NOT NULL,
    CONSTRAINT [PK_Product] PRIMARY KEY ([Id])

【注意】:此时将其映射成枚举没毛病上述已经演示,但是当我们获取数据时将TINYINT转换成枚举时将出现如下错误:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

说到底TINYINT对应C#中的byte类最后尝试将其转换为int才会导致转换失败,所以在定义枚举时记得将其继承自byte,如下才好使:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

    public enum Type : byte
    {
        [Description("普通")]
        General = 0,
        [Description("保险")]
        Insurance = 1
    }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

讲完如上映射后,我们再来讲讲默认值映射。 当我们敲默认映射会发现有两个,一个是HasDefaultValue,一个是HasDefaultValueSql,我们一起来看看到底如何用:

我们在Product类中添加Count字段:

 public int Count { get; set; }
b.Property(p => p.Count).HasDefaultValue(0);

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

如上是对于int类型如上设置,如果是枚举类型呢,我们来试试:

 b.Property(p => p.Type)
                .IsRequired()
                .HasColumnType("TINYINT").HasDefaultValue(0);

此时迁移将出现如下错误:

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

也就是说无法将枚举值设置成int类型,此时我们应该利用HasDefaultValue


感谢花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨一下吧,让楼主能喝上一杯咖啡,在此谢过了!
如果您觉得阅读本文对您有帮助,请点一下“”按钮,您的将是我最大的写作动力!
本文版权归作者和博客园共有,来源网址:欢迎各位转载,但是未经作者本人同意,转载文章之后,否则保留追究法律责任的权利。

http://www.cnblogs.com/CreateMyself/p/6995403.html