如何在EF Core中插入外键阴影属性?



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调用将失败。

使用这种方法的一个注意事项是,与获取实际的Blog条目不同,创建的Blog 不是一个完整的Blog,因此可以发送Post或其他可能需要发布的地方。博客要得到完整的博客(比如得到名字、作者等)就不能看到关于博客的任何细节。只有当您信任输入,并且创建的实体除了在DB中创建所需的行之外没有被引用时,才应该使用此方法。

正如阴影和索引器属性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();

最新更新