0
点赞
收藏
分享

微信扫一扫

Skywalking的Helm Chart方式部署

斗米 03-23 19:30 阅读 2

文章目录

一对多

文章实体类Article、评论实体类Comment。一篇文章对应多条评论。

实体类:

public class Article
{
	public long Id { get; set; }
	public string Title { get; set; }
	public string Content { get; set; }
	public List<Comment> Comments { get; set; } 
                       = new List<Comment>(); 
}

public class Comment
{
	public long Id { get; set; }
	public Article Article { get; set; }
	public string Message { get; set; }
}

关系配置:
EF Core中实体之间关系的配置的套路:
HasXXX(…).WithXXX(…);
有XXX、反之带有XXX。
XXX可选值One、Many。

一对多:HasOne(…).WithMany(…);
一对一:HasOne(…).WithOne (…);
多对多:HasMany (…).WithMany(…);

class ArticleConfig : IEntityTypeConfiguration<Article>
{
	public void Configure(EntityTypeBuilder<Article> builder)
	{
		builder.ToTable("T_Articles");
		builder.Property(a => a.Content).IsRequired().IsUnicode();
		builder.Property(a => a.Title).IsRequired().IsUnicode().HasMaxLength(255);
	}
}
class CommentConfig : IEntityTypeConfiguration<Comment>
{
	public void Configure(EntityTypeBuilder<Comment> builder)
	{
		builder.ToTable("T_Comments");
		builder.HasOne<Article>(c=>c.Article).WithMany(a => a.Comments).IsRequired();
		builder.Property(c=>c.Message).IsRequired().IsUnicode();
	}
}
class TestDbContext : DbContext
{
    public DbSet<Article> Articles { get; set; }
    public DbSet<Comment> Comments { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        string connStr = "Server=.;Database=demo3;Trusted_Connection=True";
        optionsBuilder.UseSqlServer(connStr);
        optionsBuilder.LogTo(Console.WriteLine);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    }
}
Article a1 = new Article();
a1.Title = "微软发布.NET 20的首个预览";
a1.Content = "微软昨日在一篇官网博客文章中宣布了 .NET 20 首个预览版本的到来。";
Comment c1 = new Comment() { Message = "支持" };
Comment c2 = new Comment() { Message = "微软太牛了" };
Comment c3 = new Comment() { Message = "火钳刘明" };
a1.Comments.Add(c1);
a1.Comments.Add(c2);
a1.Comments.Add(c3);
using TestDbContext ctx = new TestDbContext();
ctx.Articles.Add(a1);
await ctx.SaveChangesAsync();

不需要显式为Comment对象的Article属性赋值(当前赋值也不会出错),也不需要显式地把新创建的Comment类型的对象添加到DbContext中。EF Core会自动识别。

一对多关系数据的获取

Article a = ctx.Articles.Include(a=>a.Comments).Single(a=>a.Id==1); //生成的是left join
Console.WriteLine(a.Title);
foreach(Comment c in a.Comments)
{
	Console.WriteLine(c.Id+":"+c.Message);
}
// 通过Comments查询Article也是相同的写法,生成的是inner join

Include定义在Microsoft.EntityFrameworkCore命名空间中。

获取指定字段

var a1 = dbContext.Articles.Select(x => new {x.Id, x.Title}).First();

只获取外键id

1、在实体类中显式声明一个外键属性。
2、关系配置中通过HasForeignKey(c=>c.ArticleId)指定这个属性为外键。

class CommentConfig : IEntityTypeConfiguration<Comment>
{
	public void Configure(EntityTypeBuilder<Comment> builder)
	{
		builder.ToTable("T_Comments");
		builder.HasOne<Article>(c => c.Article).WithMany(a => a.Comments)
			.IsRequired().HasForeignKey(c => c.ArticleId);
		builder.Property(c => c.Message).IsRequired().IsUnicode();
	}
}

3、除非必要,否则不用声明,因为会引入重复(冗余)。

导航属性

由一个属性可以访问到另外一种类型的实体叫做导航属性

在这里插入图片描述

在这里插入图片描述

单向导航:不设置反向的属性,然后配置的时候WithMany()不设置参数即可。

//请假配置类
class LeaveConfig : IEntityTypeConfiguration<Leave>
{
	public void Configure(EntityTypeBuilder<Leave> builder)
	{
		builder.ToTable("T_Leaves");
		//申请人
		builder.HasOne<User>(l => l.Requester).WithMany();
		//审批人
		builder.HasOne<User>(l => l.Approver).WithMany();
		builder.Property(l => l.Remarks).HasMaxLength(1000).IsUnicode();
	}
}
using TestDbContext ctx = new TestDbContext();
User u = await ctx.Users.SingleAsync(u => u.Name == "张三");
foreach (var l in ctx.Leaves.Where(l => l.Requester == u))
{
	Console.WriteLine(l.Remarks);
}

如果表属于被很多表引用的基础表,则用单向导航属性,否则可以自由决定是否用双向导航属性。

自引用的组织结构树

class OrgUnit
{
	public long Id { get; set; }
	public string Name { get; set; }
	public OrgUnit Parent { get; set; }
	public List<OrgUnit> Children { get; set; } = new List<OrgUnit>();
}
builder.ToTable("T_OrgUnits");
builder.Property(o => o.Name).IsRequired().IsUnicode().HasMaxLength(100);
builder.HasOne<OrgUnit>(u => u.Parent).WithMany(p => p.Children);

一对一

//订单实体
class Order
{
	public long Id { get; set; }
	public string Name { get; set; }
	public string Address { get; set; }
	public Delivery Delivery { get; set;} 
}
//快递单实体
class Delivery
{
	public long Id { get; set; }
	public string CompanyName { get; set; }
	public String Number { get; set; }
	public Order Order { get; set; }
	public long OrderId { get; set; }
}

OrderConfig:

builder.HasOne<Delivery>(o => o.Delivery).WithOne(d => d.Order).HasForeignKey<Delivery>(d=>d.OrderId);

多对多

class Student
{
	public long Id { get; set; }
	public string Name { get; set; }
	public List<Teacher> Teachers { get; set; } = new List<Teacher>();
}
class Teacher
{
	public long Id { get; set; }
	public string Name { get; set; }
	public List<Student> Students { get; set; } = new List<Student>();
}	

StudentConfig:

builder.ToTable("T_Students");
builder.HasMany<Teacher>(s => s.Teachers).WithMany(t=>t.Students).UsingEntity(j=>j.ToTable("T_Students_Teachers"));
//执行迁移后,会自动生成T_Students_Teachers中间表

基于关系的复杂查询

查询评论中含有“微软”的所有的文章:

ctx.Articles.Where(a=>a.Comments.Any(c=>c.Message.Contains("微软")));
//变换成另一种写法。
ctx.Comments.Where(c => c.Message.Contains("微软"))
.Select(c => c.Article).Distinct();

同样效果的代码可能有多种写法,有时候要关注底层的SQL,看哪种方式最好。

查询“所有由蜗牛快递负责的订单信息”:

ctx.Orders.Where(o=>o.Delivery.CompanyName== "蜗牛快递");

有了IEnumerable还要IQueryable?

普通集合的版本(IEnumerable)是在内存中过滤(客户端评估),而IQueryable版本则是把查询操作翻译成SQL语句(服务器端评估)

IQueryable<Book> books = ctx.Books;
books.Where(b => b.Price > 1.1)

IEnumerable<Book> books = ctx.Books;
books.Where(b => b.Price > 1.1)

IQueryable的延迟执行

1、IQueryable只是代表一个“可以放到数据库服务器去执行的查询”,它 没有立即执行,只是“可以被执行”而已。
2、对于IQueryable接口调用非终结方法的时候不会执行查询,而 调用终结方法的时候则会立即执行查询
3、终结方法:遍历、ToArray()ToList()Min()Max()Count()等;
4、非终结方法:GroupBy()OrderBy()Include()Skip()Take()等。
5、简单判断:一个方法的返回值类型如果是IQueryable类型,那么这个方法一般就是非终结方法,否则就是终结方法。

IQueryable的复用

IQueryable<Book> books = ctx.Books.Where(b => b.Price <= 8);
Console.WriteLine(books.Count());
Console.WriteLine(books.Max(b=>b.Price));
var books2 = books.Where(b=>b.PubTime.Year>2000); //执行条件:Price <= 8 && > PubTime.Year>2000

IQueryable底层是如何读取数据的

1、DataReader:分批从数据库服务器读取数据。内存占用小、 DB连接占用时间长;
2、DataTable:把所有数据都一次性从数据库服务器都加载到客户端内存中。内存占用大,节省DB连接。

using TestDbContext ctx = new TestDbContext();
foreach (Articles a in ctx.Articles)
{
	//DataReader分批读取
	Console.WriteLine(a.Id + ":" + a.Title);
	foreach (Comment c in ctx.Comments)
	{
		Console.WriteLine(c.Id + ":" + c.Message);
	}
}
using TestDbContext ctx = new TestDbContext();
foreach (Articles a in ctx.Articles.ToArray())
{
	//一次性查询出来数据
	Console.WriteLine(a.Id + ":" + a.Title);
	foreach (Comment c in ctx.Comments.ToArray())
	{
		Console.WriteLine(c.Id + ":" + c.Message);
	}
}

Tips:很多数据库的 ADO.NET Core Provider是不支持多个DataReader同时执行的。这时候就需要使用终结方法查询。

EF Core中的异步方法

SaveChanges()SaveChangesAsync()AddAsync()AddRangeAsync()AllAsync()AnyAsyncAverageAsyncContainsAsyncCountAsyncFirstAsyncFirstOrDefaultAsyncForEachAsyncLongCountAsyncMaxAsyncMinAsyncSingleAsyncSingleOrDefaultAsyncSumAsync

如何异步遍历IQueryable

1、ToListAsync()ToArrayAsync()。结果集不要太大。
2、await foreach (Book b in ctx.Books.AsAsyncEnumerable())

EF Core执行非查询原生SQL语句

ctx.Database.ExecuteSqlInterpolatedAsync(@$"insert into T_Books(Title,PubTime,Price,AuthorName)
select Title, PubTime, Price,{aName} from T_Books
where Price > {price}");

字符串内插 如果赋值给string变量,就是字符串拼接;字符串内插如果赋值给FormattableString变量,编译器就会构造FormattableString对象。ExecuteSqlInterpolatedAsync()的参数是FormattableString类型。因此ExecuteSqlInterpolatedAsync会进行参数化SQL的处理,故不会造成sql注入。

执行实体相关查询原生SQL语句

IQueryable<Book> books = ctx.Books.FromSqlInterpolated(@$"select * from T_Books
		where DatePart(year,PubTime)>{year}
		order by newid()");
//FromSqlInterpolated()方法的返回值是IQueryable类型的,因此我们可以在实际执行IQueryable之前,对IQueryable进行进一步的处理。
foreach(Book b in books.Skip(3).Take(6))
{

}

局限性:

  • SQL 查询必须返回实体类型对应数据库表的所有列;
  • 结果集中的列名必须与属性映射到的列名称匹配。
  • 只能单表查询,不能使用Join语句进行关联查询。但是可以在查询后面使用Include()来进行关联数据的获取。

执行任意原生SQL查询语句

方式1、dbCxt.Database.GetDbConnection()获得ADO.NET Core的数据库连接对象

DbConnection conn = ctx.Database.GetDbConnection();
if (conn.State != ConnectionState.Open)
{
	conn.Open();
}
using (var cmd = conn.CreateCommand())
{
	cmd.CommandText = @"xxx";
	var p1 = cmd.CreateParameter();
	p1.ParameterName = "@year";
	p1.Value = year;
	cmd.Parameters.Add(p1);
	using (var reader = cmd.ExecuteReader())
}

方式2、用Dapper等框架执行原生复杂查询SQL

EFCore如何跟踪实体数据变化

实体的状态:

  • 已添加(Added):DbContext正在跟踪此实体,但数据库中尚不存在该实体。
  • 未改变(Unchanged):DbContext正在跟踪此实体,该实体存在于数据库中,其属性值和从数据库中读取到的值一致,未发生改变。
  • 已修改(Modified):DbContext正在跟踪此实体,并存在于数据库中,并且其部分或全部属性值已修改。
  • 已删除(Deleted):DbContext正在跟踪此实体,并存在于数据库中,但在下次调用 SaveChanges 时要从数据库中删除对应数据。
  • 已分离(Detached):DbContext未跟踪该实体。

SaveChanges()的操作:

  • “已分离”和“未改变”的实体,SaveChanges()忽略;
  • “已添加”的实体,SaveChanges() 插入数据库;
  • “已修改”的实体,SaveChanges() 更新到数据库;
  • “已删除”的实体,SaveChanges() 从数据库删除;

查看实体的状态:

DbContext会根据跟踪的实体的状态,在SaveChanges()的时候,根据实体状态的不同,生成Update、Delete、Insert等SQL语句,来把内存中实体的变化更新到数据库中。

EF Core优化之AsNoTracking

如果通过DbContext查询出来的对象只是用来展示,不会发生状态改变,则可以使用AsNoTracking()来 “禁用跟踪”。

Tips:如果查询出来的对象不会被修改、删除等,那么查询时可以AsNoTracking(),就能降低内存占用。

实体状态跟踪的妙用

常规更新需要先查询、再更新,两条SQL。利用修改实体状态可以直接更新数据库

Book b1 = new Book {Id=10};//跟踪通过Id定位
b1.Title = "zhangsan";
var entry1 = ctx.Entry(b1);
entry1.Property("Title").IsModified = true;
Console.WriteLine(entry1.DebugView.LongView);
ctx.SaveChanges();

常规删除需要先查询、再删除,两条SQL。利用修改实体状态可以直接删除

Book b1 = new Book { Id = 28 };
ctx.Entry(b1).State = EntityState.Deleted;
ctx.SaveChanges();

Tips:修改实体状态带来的性能提升微乎其微,对代码可读性、可维护性不强,代码可读性、可维护性不强。

全局查询筛选器

使用场景:软删除、多租户

builder.HasQueryFilter(b=>b.IsDeleted==false);

忽略全局查询筛选器:

ctx.Books.IgnoreQueryFilters().Where(b => b.Title.Contains("o")).ToArray()

表达式树

//Expression<TDelegate>类型
//从Lambda表达式来生成表达式树:
Expression<Func<Book, bool>> e1 = b =>b.Price > 5;
//普通委托:Func<Book, bool> e = b => b.Price > 5;

Expression对象储存了运算逻辑,它把运算逻辑保存成抽象语法树(AST),可以在运行时动态获取运算逻辑。而普通委托则没有。

查看表达式树的结构

1、ExpressionTreeVisualizer VS插件
2、nuget:Install-Package ExpressionTreeToString

Expression<Func<Book, bool>> e = b => b.AuthorName.Contains("牛炸了")||b.Price>30;
Console.WriteLine(e.ToString("Object notation", "C#"));

通过代码动态构造表达式树

生成和如下硬编码的C#代码一样的表达式树:

Expression<Func<Book, bool>> e = b =>b.Price > 5;
ParameterExpression paramB = Expression.Parameter(typeof(Book),"b");
MemberExpression exprLeft = Expression.MakeMemberAccess(paramB, typeof(Book).GetProperty("Price"));
ConstantExpression exprRight = Expression.Constant(5.0,typeof(double));
BinaryExpression exprBody = Expression.MakeBinary(ExpressionType.GreaterThan, exprLeft, exprRight);
Expression<Func<Book, bool>> expr1 = Expression.Lambda<Func<Book, bool>>(exprBody, paramB);

Add:加法;AndAlso:短路与运算;ArrayAccess:数组元素访问;Call:方法访问;Condition:三元条件运算符;
Constant:常量表达式;Convert:类型转换;
GreaterThan:大于运算符;
GreaterThanOrEqual:大于或等于运算符;
MakeBinary:创建二元运算;NotEqual:不等于运算;
OrElse:短路或运算;Parameter:表达式的参数;

Tips:一般只有在编写不特定于某个实体类的通用框架的时候,由于无法在编译器确定要操作的类名、属性等,所以才需要编写动态构建表达式树的代码。否则为了提高代码的可读性和可维护性,要尽量避免动态构建表达式树。而是用IQueryable的延迟执行特性来动态构造。

System.Linq.Dynamic.Core

nuget安装:System.Linq.Dynamic.Core

1、System.Linq.Dynamic.Core
2、使用字符串格式的语法来进行数据操作

var query = db.Customers
    .Where("City == @0 and Orders.Count >= @1", "London", 10)
    .OrderBy("CompanyName")
    .Select("new(CompanyName as Name, Phone)");
举报

相关推荐

0 条评论