EF Core允许我们省略外键属性,因为导航属性的存在足以在两个实体之间建立关系。然后EF Core会在它自己的底层创建一个外键shadow属性。
我将使用一个类似于文档中的例子:
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public Blog Blog { get; set; }
}
现在,给定这样的结构,其中没有显式的外键属性,如何为具有给定ID的Blog
创建新的Post
?
我试过了:
db.Posts.Add(new Post() {
Title = "Some title",
Course = new Blog { Id = 1234 },
});
db.SaveChanges();
但它似乎不起作用。这似乎是一个简单的问题,但令我惊讶的是,无论是在文档中还是在我访问的其他任何地方都没有关于它的信息。
如果您正在使用影子fk,那么您可以使用许多方法。首先是获取相关实体。这意味着要去数据库,但好处是,这断言您认为应该有效的Blog ID是有效的:
var blog = db.Blogs.Single(x => x.Id == blogId);
var post = new Post
{
Title = title,
Blog = blog
};
db.Posts.Add(post);
db.SaveChanges();
一般来说,你不应该相信来自客户端的任何东西。如果您确实想假设ID是有效的,并且不希望进行往返,那么安全的方法是:
var blog = db.Blogs.Local.SingleOrDefault(x => x.Id == blogId);
if (blog == null)
{
blog = new Blog { Id = blogId };
db.Attach(blog);
}
var post = new Post
{
Title = title,
Blog = blog
};
db.Posts.Add(post);
db.SaveChanges();
这将在DbContext中检查Blog的本地缓存实例(不访问DB),如果找到就使用它。如果没有找到,我们创建一个并附加它,然后将其与Post关联。我们应该在附加实例之前检查本地缓存,因为如果您跳过这一步,并且DbContext已经在跟踪具有该ID的Blog,那么Attach
调用将失败。
正如阴影和索引器属性EF Core文档主题开头所提到的:
阴影属性是在。net实体类中没有定义的属性,但是在EF Core模型中为实体类型定义了这些属性。这些属性的值和状态完全在Change Tracker中维护。
因此,一旦你将你的实体附加到上下文,你就可以使用EF Core元数据读取/写入这些属性并更改跟踪api, 一旦你知道它们的属性名称 .
通常你不这样做,或者即使你这样做,使用魔法字符串是容易出错的,通常你会失去类型安全。
幸运的是,所有需要的信息都可以在EF Core模型中获得,因此下面是一个用于通用设置阴影FK属性的示例助手方法:
namespace Microsoft.EntityFrameworkCore
{
using ChangeTracking;
using Metadata;
public static partial class EfCoreChangeTrackingExtensions
{
public static void SetForeignKey<TEntity, TRelated>(this ReferenceEntry<TEntity, TRelated> target, params object[] values)
where TEntity : class
where TRelated : class
{
if (target is null) throw new ArgumentNullException(nameof(target));
if (values is null) throw new ArgumentNullException(nameof(values));
var foreignKey = ((INavigation)target.Metadata).ForeignKey;
if (foreignKey.Properties.Count != values.Length) throw new ArgumentException();
for (int i = 0; i < values.Length; i++)
target.EntityEntry.CurrentValues[foreignKey.Properties[i]] = values[i];
}
}
}
为您的场景提供示例用法:
var postEntry = db.Posts.Add(new Post() {
Title = "Some title",
});
postEntry.Reference(e => e.Blog).SetForeignKey(1234);
db.SaveChanges();