EF Core – Owned Entity Types & Complex Types
前言
EF Core 8.0 推出了 Complex Types,这篇要来介绍一下。
由于它和 Owned Entity Types 傻傻分不清楚,加上我之前也没有写过 Owned Entity Types 的文章,所以这篇就一起介绍呗。
Owned Entity Types
Owned Entity Types 本质上任然属于一种 Entity Types,只是它有一些潜规则,所以变得和普通 Entity Type 有所区别。
Owned Entity Types 在 Domain-driven design (领域驱动设计) 里被视作为 Aggregate 的实现。很遗憾,我对 DDD 一窍不通,无法用 DDD 视角去解释它。
Compare with one-to-one relationship
我们拿 one-to-one relationship 来做对比,这样就可以看出 Owned Entity Types 的特色了。
首先,做两个 Entity -- Order 和 OrderCustomerInfo
public class Order
{
public int Id { get; set; }
public OrderCustomerInfo CustomerInfo { get; set; } = null!;
public decimal Amount { get; set; }
} public class OrderCustomerInfo
{
public int Id { get; set; }
public Order Order { get; set; } = null!;
public string Name { get; set; } = "";
public string Phone { get; set; } = "";
}
它们是一对一关系
public class ApplicationDbContext() : DbContext()
{
public DbSet<Order> Orders => Set<Order>();
public DbSet<OrderCustomerInfo> OrderCustomerInfos => Set<OrderCustomerInfo>(); protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(
builder =>
{
builder.ToTable("Order");
builder.Property(e => e.Amount).HasPrecision(19, 2);
builder.HasOne(e => e.CustomerInfo).WithOne(e => e.Order).HasForeignKey<OrderCustomerInfo>(e => e.Id);
}); modelBuilder.Entity<OrderCustomerInfo>(
builder =>
{
builder.ToTable("OrderCustomerInfo");
builder.Property(e => e.Name).HasMaxLength(256);
builder.Property(e => e.Phone).HasMaxLength(256);
});
}
}
接着 insert 和 query
using var db = new ApplicationDbContext(); // insert order with customer info
db.Orders.Add(new()
{
Amount = 100,
CustomerInfo = new()
{
Name = "Derrick",
Phone = "+60 16-773 7062",
},
});
db.SaveChanges(); // query
var order = db.Orders
.Include(e => e.CustomerInfo)
.First(); Console.WriteLine(order.CustomerInfo.Name); // "Derrick"
Owned Entity Types 的特性
对比 one-to-one,Owned Entity Types 有几个特性:
Owned Entity Types 没有 DbSet
OrderCustomerInfo 不是独立的 Entity,它属于 part of the Order Entity,所以它没有 DbSet。
这也导致了它不能直接被创建,下面这句是不成立的
db.OrderCustomerInfos.Add() // 'ApplicationDbContext' does not contain a definition for 'OrderCustomerInfos'
OrderCustomerInfo 需要依附在 Order 上才能一起被创建,像这样
db.Orders.Add(new()
{
Amount = 100,
CustomerInfo = new()
{
Name = "Derrick",
Phone = "+60 16-773 7062",
},
});自动 Include
没有 DbSet 自然也无法直接 query
db.OrderCustomerInfos.ToList() // 'ApplicationDbContext' does not contain a definition for 'OrderCustomerInfos'
要获取 OrderCustomerInfo 只能透过 Order。另外 Owned Entity Types 有一个特色 -- 它会自动被 Include 出来。
即便我们没有写 Include,Owned Entity Types 依然会被 eager loading 出来。
same Table
默认情况下,Owned Entity Types (OrderCustomerInfo) 会和它依附的 Entity Types (Order) 存放在同一个数据库 Table,还有 column name 会加上 prefix,这就像使用了 Table Splitting 的那样。
Id become shadow property
Owned Entity Types 任然是 Entity Types,它依然有 primary key 的概念,只是它改成了 Shadow Property,在 class 会看不见 Id。
Config Owned Entity Types
替换成这样
modelBuilder.Entity<Order>(
builder =>
{
builder.ToTable("Order");
builder.Property(e => e.Amount).HasPrecision(19, 2);
builder.OwnsOne(e => e.CustomerInfo, builder =>
{
builder.Property(e => e.Name).HasMaxLength(256);
builder.Property(e => e.Phone).HasMaxLength(256);
});
});
效果
// query
var order = db.Orders.First(); Console.WriteLine(order.CustomerInfo.Name); // "Derrick"
不需要 Include,它会自动 Include。
数据库 Order Table
OrderCustomerInfo 的属性被映射到 Order Table,而且 column name 加了 prefix "CustomerInfo" 这名字来自 Order.CustomerInfo 属性。
我们调 Entity Model 出来证实一下,Owned Entity Types 也是一种 Entity Types 而且它其实是有 Key 的。
using var db = new ApplicationDbContext();
var customerEntityType = db.Model.FindEntityType(typeof(OrderCustomerInfo))!; // Owned Entity Types 也是一种 Entity Types
var isOwned = customerEntityType.IsOwned(); // true,OrderCustomerInfo 是 Owned Entity Types
var property = customerEntityType.GetProperty("OrderId")!;
Console.WriteLine(property.IsShadowProperty()); // true,"OrderId" 是 Shadow Property
Console.WriteLine(property.IsKey()); // true,"OrderId" 是 Primary Key
Console.WriteLine(property.IsForeignKey()); // true,"OrderId" 是 Foreign Key var orderEntityType = db.Model.FindEntityType(typeof(Order))!;
Console.WriteLine(orderEntityType.FindNavigation("CustomerInfo")!.ForeignKey); // "OrderId", CustomerInfo 属性不是普通的 Property 而是 Navigation
Two Table
默认情况下 Owned Entity Types (OrderCustomerInfo) 会和它的 Owner (Order) 共用一个 Table。
如果我们不希望这样,则可以使用 Entity Splitting 将它们分开成两个 Table。
column name prefix 会自动被拿掉。
效果
Sharing Owned Entity Types
Owned Entity Types 最好不要共用,不顺风水。
public class Address
{
public string Street1 { get; set; } = "";
public string Street2 { get; set; } = "";
public string PostalCode { get; set; } = "";
public string Country { get; set; } = "";
} public class Order
{
public int Id { get; set; }
public Address BillingAddress { get; set; } = null!;
public Address ShippingAddress { get; set; } = null!;
public decimal Amount { get; set; }
} public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
public Address Address { get; set; } = null!;
}
Order 和 Customer 都使用了 Address。
试想,如果是一对一关系,上面这样做成立吗?
答案是不成立,因为一对一关系是靠 id 作为 foreign key 维持关系的,上面这样就乱了套了。
我们测试看强制设置 Owned Entity Types 出来的效果是怎样的。
public class ApplicationDbContext() : DbContext()
{
public DbSet<Order> Orders => Set<Order>();
public DbSet<Customer> Customers => Set<Customer>(); protected override void OnModelCreating(ModelBuilder modelBuilder)
{
static void BuildAction<TEntity>(OwnedNavigationBuilder<TEntity, Address> builder) where TEntity : class
{
builder.Property(e => e.Street1).HasMaxLength(256);
builder.Property(e => e.Street2).HasMaxLength(256);
builder.Property(e => e.PostalCode).HasMaxLength(256);
builder.Property(e => e.Country).HasMaxLength(256);
} modelBuilder.Entity<Order>(
builder =>
{
builder.ToTable("Order");
builder.Property(e => e.Amount).HasPrecision(19, 2);
builder.OwnsOne(e => e.BillingAddress, BuildAction);
builder.OwnsOne(e => e.ShippingAddress, BuildAction);
}); modelBuilder.Entity<Customer>(
builder =>
{
builder.ToTable("Customer");
builder.Property(e => e.Name).HasMaxLength(256);
builder.OwnsOne(e => e.Address, BuildAction);
});
}
}
BuildAction 是共用的。
效果
如果做 Entity Splitting 的话,就会多出 3 个 Table -- CustomerAddress,OrderBillingAddress,OrderShippingAddress。
从这个结构可以看出,它底层依然是一对一的概念,只是在上层搞了许多映射。
我们进资料看看
using var db = new ApplicationDbContext();
var address = new Address
{
Street1 = "test",
Street2 = "test",
PostalCode = "81300",
Country = "Malaysia"
};
db.Orders.Add(new()
{
Amount = 100,
ShippingAddress = address,
BillingAddress = address
});
db.SaveChanges();
注意,shippingAddress 和 billingAddress 使用了同一个 address 对象。
运行结果是报错
因为官网声明了,Owned Entity Types 实例是不可以共享的,必须一个对一个。
我觉得 EF Core 如果硬硬要做映射是可以做到的,只是他们认为这种使用方式已经脱离了 Owned Entity Types 的本意,所以才不支持它。
但是,EF Core 8.0 推出的 Complex Types 支持这种使用方式,Complex Types 和 Owned Entity Types 有几分相似下一 part 会详细讲。
Collections of owned types
Owned Entity Types 不仅仅可以映射一对一关系,一对多关系也可以映射。
但是一对多就不可能共用同一个数据库 Table 了,一定是 2 个 Table。
其它特性,比如没有 DbSet,自动 Include,不能 share instance 这些则都一样。
来个简单的示范
Entity
public class Address
{
public string Street1 { get; set; } = "";
public string Street2 { get; set; } = "";
public string PostalCode { get; set; } = "";
public string Country { get; set; } = "";
} public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
public List<Address> Addresses { get; set; } = [];
}
ModelBuilder
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>(
builder =>
{
builder.ToTable("Customer");
builder.Property(e => e.Name).HasMaxLength(256);
builder.OwnsMany(e => e.Addresses, builder =>
{
builder.Property(e => e.Street1).HasMaxLength(256);
builder.Property(e => e.Street2).HasMaxLength(256);
builder.Property(e => e.PostalCode).HasMaxLength(256);
builder.Property(e => e.Country).HasMaxLength(256);
});
});
}
数据库
和普通一对多的表结构是一样的。
using var db = new ApplicationDbContext();
var addressEntityType = db.Model.FindEntityType(typeof(Address))!;
addressEntityType.GetProperty("Id").IsKey(); // true
addressEntityType.GetProperty("Id").IsShadowProperty(); // true
addressEntityType.GetProperty("CustomerId").IsForeignKey(); // true
addressEntityType.GetProperty("CustomerId").IsShadowProperty(); // true
CustomerId 是 Foreign Key,Id 是 Primary Key,它们都是 Shadow Property。
如果想修改 Primary Key 和 Foreign Key 可以这样配置
builder.OwnsMany(e => e.Addresses, builder =>
{
builder.WithOwner().HasForeignKey("CustomerId2"); // rename foreign key CustomerId to CustomerId2
builder.Property<int>("Id").HasColumnName("AddressId"); // rename primary key Id to AddressId
});
How to detech Owned Entity Types modified?
相关 Issue:
Detecting if an owned entity was changed
Return true for IsModified on navigations pointing to Added or Deleted entities
How can I get a deleted owned entity from the parent entity?
我们来看一个简单的例子
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public Address Address { get; set; } = null!;
} public class Address
{
public string Line1 { get; set; } = null!;
}
Address 是 Owned Entity Type。
var product = db.Products.AsTracking().Single();
product.Address.Line1 = "new line";
var isProductChanged = db.Entry(product).State == EntityState.Modified; // false
var isAddressPropertyModified = db.Entry(product).Reference(e => e.Address).IsModified; // false
var isAddressChanged = db.Entry(product.Address).State == EntityState.Modified; // true
var isLine1PropertyModified = db.Entry(product.Address).Property(e => e.Line1).IsModified; // true
我们从 product 对象进入到 address 对象里,然后修改 line1 的值。
可以看到,从 product entry 是完全感知不到任何 modified,只有 product.address entry 才能感知到 modified。
我们换一个修改方式
await using var db = new ApplicationDbContext(); var product = db.Products.AsTracking().Single();
var oldAddress = product.Address;
var newAddress = new Address { Line1 = "new line" };
product.Address = newAddress; var isProductChanged = db.Entry(product).State == EntityState.Modified; // false
var isAddressPropertyModified = db.Entry(product).Reference(e => e.Address).IsModified; // true
var isAddressChanged = db.Entry(product.Address).State == EntityState.Modified; // false
var isLine1PropertyModified = db.Entry(product.Address).Property(e => e.Line1).IsModified; // false
var isOldAddressDeleted = db.Entry(oldAddress).State == EntityState.Deleted; // true
var isNewAddressAdded = db.Entry(oldAddress).State == EntityState.Added; // true
新旧 address 对象替换,这样操作的话属于 add and delete,同时 product entry 的 address reference 会感知到 modified (注:但是 product entry 依旧是 unchanged 哦)。
To JSON
Owned Entity Types 还有一个强项,它可以以 JSON 格式保存到数据库里,不管是 OwnsOne 还是 OwnsMany 都行。
builder.OwnsMany(e => e.Addresses, builder =>
{
builder.ToJson();
builder.Property(e => e.Street1).HasMaxLength(256);
builder.Property(e => e.Street2).HasMaxLength(256);
builder.Property(e => e.PostalCode).HasMaxLength(256);
builder.Property(e => e.Country).HasMaxLength(256);
});
加一句 ToJson() 就可以了。
测试
using var db = new ApplicationDbContext();
db.Customers.Add(new()
{
Name = "Derrick",
Addresses = [
new() { Street1 = "test1", Street2 = "test1", PostalCode = "81300", Country = "Malaysia" },
new() { Street1 = "test2", Street2 = "test2", PostalCode = "123456", Country = "Singapore" }
]
});
db.SaveChanges();
效果
厉害吧
Nested Owned Entity Types
Owned Entity Types 支持嵌套,配置的方式和上面一样,在 OwnsOne / Many 里面继续调用 OwnsOne / Many 就可以了。
这里我就不演示了。
另外,ToJson 的话,一定要在最上层调用。
目前不支持一半一半的状况,要 JSON 就得从上层到下层通通 JSON。
Limitation
目前不支持 inheritance hierarchies 继承结构,TPH、TPT、TPC 通通不支持,希望未来会支持。
Complex Types
参考:Docs – Value objects using Complex Types
EF Core 8.0 推出了 Complex Types。Complex Types 和 Owned Entity Types 外观有点像,但内在思想是不同的。
Complex Types 在 Domain-driven design (领域驱动设计) 里被视作为 Value Object 的实现。很遗憾,我对 DDD 一窍不通,无法用 DDD 视角去解释它。
Current limitations
目前 Complex Types 还不算完整,它有很多该有得功能都还没有实现,要使用它要小心哦。
不支持一对多关系。
不支持 nullable
public OrderCustomerInfo? CustomerInfo { get; set; }
像上面这样 OrderCustomerInfo 将无法设置成 Complex Types,因为它是 nullable。
不支持 ToJson。
这几个都挺需要的,既然都没有
和 Owned Entity Types 的共同点和区别
共同点:
没有 DbSet,不可以直接 Add 和 Query
- 自动 Include
区别:
Complex Types 不支持一对一映射到 two table。
Complex Types 不继承 Entity Types,因为它完全没有 Primary Key 概念。
Complex Types 的实例是可以共用的。
- Complex Types 更倾向使用 record 而非 class。
Config Complex Types
和 config Owned Entity Types 大同小异。
Entity
public class Order
{
public int Id { get; set; }
public OrderCustomerInfo CustomerInfo { get; set; } = null!;
public decimal Amount { get; set; }
} public class OrderCustomerInfo
{
public string Name { get; set; } = "";
public string Phone { get; set; } = "";
}
ModelBuilder
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(
builder =>
{
builder.ToTable("Order");
builder.Property(e => e.Amount).HasPrecision(19, 2);
builder.ComplexProperty(e => e.CustomerInfo, builder =>
{
builder.Property(e => e.Name).HasMaxLength(256);
builder.Property(e => e.Phone).HasMaxLength(256);
});
});
}
效果
Entity Model
using var db = new ApplicationDbContext();
var customerInfoEntityType = db.Model.FindEntityType(typeof(OrderCustomerInfo)); // null
var orderEntityType = db.Model.FindEntityType(typeof(Order))!;
var customerInfoProperty = orderEntityType.FindProperty("CustomerInfo"); // null
var customerInfoComplexProperty = orderEntityType.FindComplexProperty("CustomerInfo");
OrderCustomerInfo 不是 Entity Types,所以 FindEntityType 是找不到的,这点和 Owned Entity Types 不同。
另外,CustomerInfo 也不是普通的 Property 也不是 Navigation,而是 ComplexProperty,要使用 FindComplexProperty 才能获取到,这点也和 Owned Entity Types 不同。
DbContext.Entry
Entity Model 不一样,那访问 Entry CurrentValue,IsModified 这些自然也不一样了。
Entity
public class Order
{
public int Id { get; set; }
public OrderCustomerInfo CustomerInfo { get; set; } = null!;
public Address ShippingAddress { get; set; } = null!;
public decimal Amount { get; set; }
}
假设 OrderCustomerInfo 是 Complex Types,ShippingAddress 是 Owned Entity Types
想访问 ShippingAddress 的 CurrentValue 通过 DbContext.Entry 就可以了。
var order = db.Orders.First();
var shippingAddressEntry = db.Entry(order.ShippingAddress);
var countryValue = shippingAddressEntry.Property(e => e.Country).CurrentValue;
想访问 CustomerInfo 的 CurrentValue 通过 DbContext.Entry 会报错。
var customerInfoEntry = db.Entry(order.CustomerInfo); // error
因为 Complex Types 不是 Entity Types,它没有 Key 的概念。
正确的方式是先进入 OrderEntry 然后使用 ComplexProperty 找到 CustomerInfo 在进入它的 Property 获取 CurrentValue
var orderEntry = db.Entry(order);
var nameValue = orderEntry.ComplexProperty(e => e.CustomerInfo).Property(e => e.Name).CurrentValue
Immutable
Value Object 虽然是对象,但它通常是值类型 (Immutable)。
所以一般都是使用 struct / record 而不是 class。(虽然 EF Core 支持使用 class 而且即使不是 Immutable 它也可以 tracking 到 changes)
Entity
public class Order
{
public int Id { get; set; }
public OrderCustomerInfo CustomerInfo { get; set; } = null!;
public decimal Amount { get; set; }
} public record OrderCustomerInfo(string Name, string Phone);
Add Order
using var db = new ApplicationDbContext();
db.Orders.Add(new()
{
Amount = 100,
CustomerInfo = new("Derrick", "+60 16-773 7062")
});
db.SaveChanges();
update CustomerInfo
var order = db.Orders.First();
order.CustomerInfo = order.CustomerInfo with { Name = "New Name" };
db.SaveChanges();
个人觉得使用 record 会比使用 class 更合适。不熟悉 record 的朋友可以看这篇 C# – Record, Class, Struct
总结
我们可以把 Owned Entity Types 视为变种的 one-to-one 和 one-to-many。
它最大的好处是自动 Include,自动 Table Splitting,而且可以 ToJson,底层思想继承自 one-to-one 和 one-to-many Entity Types 也算很好理解。
目前只有一个缺陷 -- 不支持继承结构。
Complex Types 不是 one-to-one 和 one-to-many 的概念,它没有 Key,它不是 Entity Types。
它适合用在 Object Value,就是说这些属性确确实实是 Property 只是它们有关系所以被 group 在一起。
比如说,地址本来是一个 string "11, Jalan Merak 22, Taman Mutiara Rini, 81300 Johor Malaysia",我想把它做的有结构,
于是拆分成 Street1, Street2, PostalCode, State, Country,这种情况就适合使用 Complex Types 来表达。
EF Core – Owned Entity Types & Complex Types的更多相关文章
- 【EF Core】Entity Framework Core 批处理语句
在Entity Framework Core (EF Core)有许多新的功能,最令人期待的功能之一就是批处理语句.那么批处理语句是什么呢?批处理语句意味着它不会为每个插入/更新/删除语句发送单独的请 ...
- Entity Framework (EF) Core工具创建一对多和多对多的关系
一. EntirtyFramework(EF)简介 EntirtyFramework框架是一个轻量级的可扩展版本的流行实体框架数据访问技术,微软官方提供的ORM工具让开发人员节省数据库访问的代码时间 ...
- 在EF Core里面如何使用以前EntityFramework的DbContext.Database.SqlQuery<SomeModel>自定义查询
问: With Entity Framework Core removing dbData.Database.SqlQuery<SomeModel> I can't find a solu ...
- ASP.NET Core 配置 Entity Framework Core - ASP.NET Core 基础教程 - 简单教程,简单编程
原文:ASP.NET Core 配置 Entity Framework Core - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 配置 Entity Fram ...
- .NET 5/.NET Core使用EF Core 5连接MySQL数据库写入/读取数据示例教程
本文首发于<.NET 5/.NET Core使用EF Core 5(Entity Framework Core)连接MySQL数据库写入/读取数据示例教程> 前言 在.NET Core/. ...
- EF Core 新特性——Owned Entity Types
Owned Entity Types 首先owned entity type是EF Core 2.0的新特性. 至于什么是owned entity types,可以先把他理解为EF Core官方支持的 ...
- EFCore Owned Entity Types,彩蛋乎?鸡肋乎?之彩蛋篇
EFCore Owned Entity Types的定义 EFCore Owned Entity Types的文档在这里:https://docs.microsoft.com/zh-cn/ef/cor ...
- EFCore Owned Entity Types,彩蛋乎?鸡肋乎?之鸡肋篇
鸡肋 鸡肋(Chicken ribs),现代汉语词语,出自<三国志·魏书·武帝纪>裴松之注引<九州春秋>曰:"夫鸡肋,弃之如可惜,食之无所得,以比汉中,知王欲还也.& ...
- Lerning Entity Framework 6 ------ Complex types
Complex types are classes that map to a subset of columns of a table.They don't contains key. They a ...
- 张高兴的 Entity Framework Core 即学即用:(一)创建第一个 EF Core 应用
写在前面 Entity Framework Core (EF Core) 是 .NET 平台流行的对象关系映射(ORM)框架.虽然 .NET 平台中 ORM 框架有很多,比如 Dapper.NHibe ...
随机推荐
- Solo 开发者周刊 (第10期):Sora 之后,谁是被遗忘的?谁又是被仰望的?
这里会整合 Solo 社区每周推广内容.产品模块或活动投稿,每周五发布.在这期周刊中,我们将深入探讨开源软件产品的开发旅程,分享来自一线独立开发者的经验和见解.本杂志开源,欢迎投稿. 好文推荐 Sol ...
- tp5生命周期
https://www.kancloud.cn/manual/thinkphp5/118011 1.入口文件 用户发起的请求都会经过应用的入口文件,通常是 public/index.php文件.当然, ...
- C# Win10缩放导致Winform字体模糊的解决方法
问题描述 现在的笔记本电脑分辨率很高,基本上能达到1920*1080以上,因为笔记本的屏幕小,在这样的分辨率下一切看着都很小,尤其是文字,根本看不清,所以Win10很人性化的提供了屏幕缩放功能,一般默 ...
- 对于同一个项目,同时将其git到GitHub和Gitee
对于同一个项目,你可以同时将其git到GitHub和Gitee.这通常通过配置多个远程仓库地址来实现.以下是一步步的操作指南: 一.在GitHub和Gitee上创建仓库 GitHub: 登录GitHu ...
- Python版WGCNA分析和蛋白质相互作用PPI分析教程
在前面的教程中,我们介绍了使用omicverse完成基本的RNA-seq的分析流程,在本节教程中,我们将介绍如何使用omicverse完成加权基因共表达网络分析WGCNA以及蛋白质相互作用PPI分析. ...
- 再读vue
app.vue是项目的主组件,页面的入口文件 main.js是项目的入口文件 vue.config.js是vue-cli的配置文件//用这个配置代理,端口号 例如 const { defineConf ...
- AS自制闹钟学习,关于PendingIntent与AlarmManager
PendingIntent是Intent的封装,不是立刻执行某个行为,而是满足某些条件或触发某些事件后才执行指定的行为实例获取一般为下列5个用法 getActivity() getActivities ...
- Jmeter二次开发函数之入门
背景:Jmeter不能满足我们的参数需求,如生成手机号码.身份证号码等业务,固对jmeter进行二次函数开发. jmeter提供了接口供用户进行二次开发,我们只需引入包进行编辑.从jmeter规范上, ...
- 【Vue】树状节点接口 与 级联选择框组件
原来有一个组织机构的渲染, 我自己写的我自己看也8太明白了: https://www.cnblogs.com/mindzone/p/14888046.html 现在,有一个位置选择,使用这个级联选择器 ...
- 【转载】SLAM领域的优秀作者与实验室汇总
原地址: https://blog.csdn.net/m0_37874102/article/details/114365837 总结一些之前看过的SLAM(VO,VIO,建图)文献所发表的实验室和作 ...