C# 带有实体框架的级联插入

C# 带有实体框架的级联插入,c#,sql,entity-framework,C#,Sql,Entity Framework,我的疑问是,为什么在这段代码中会执行3个SQL查询? 它是在FirstOrDefault中生成的 var context = new TestDbContext(); var user = context.Users.FirstOrDefault(item => item.ID == 1); user.Addresses.Add(new Address() { City = "City", Street = "Street", Postcode = "Postc

我的疑问是,为什么在这段代码中会执行3个SQL查询?

  • 它是在FirstOrDefault中生成的

    var context = new TestDbContext();
    
    var user = context.Users.FirstOrDefault(item => item.ID == 1);
    
    user.Addresses.Add(new Address()
    {
        City = "City",
        Street = "Street",
        Postcode = "Postcode",
    });
    
    context.SaveChanges();
    
    选择顶部(1) [Extent1].[ID]作为[ID], [Extent1].[EmailAddress]作为[EmailAddress] 来自[dbo].[Users]作为[Extent1] 其中1=[Extent1].[ID]
  • 它是在user.Addresses.Add中生成的 SELECT TOP (1) [Extent1].[ID] AS [ID], [Extent1].[EmailAddress] AS [EmailAddress] FROM [dbo].[Users] AS [Extent1] WHERE 1 = [Extent1].[ID] exec sp_executesql N'SELECT [Extent1].[ID]作为[ID], [Extent1].[City]作为[City], [extend1][Street]作为[Street], [扩展名1]。[邮政编码]为[邮政编码], [Extent1]。[User\u ID]作为[User\u ID] 从[dbo].[Addresses]到[Extent1] 其中([Extent1].[User\u ID]不为空) 和([Extent1].[User_ID]=@EntityKeyValue1)”,N'@EntityKeyValue1 int',@EntityKeyValue1=1
  • 它是在SaveChanges中生成的

    exec sp_executesql N'SELECT [Extent1].[ID] AS [ID], [Extent1].[City] AS [City], [Extent1].[Street] AS [Street], [Extent1].[Postcode] AS [Postcode], [Extent1].[User_ID] AS [User_ID] FROM [dbo].[Addresses] AS [Extent1] WHERE ([Extent1].[User_ID] IS NOT NULL) AND ([Extent1].[User_ID] = @EntityKeyValue1)',N'@EntityKeyValue1 int',@EntityKeyValue1=1 exec sp_executesql N'INSERT[dbo]。[地址]([城市],[街道],[邮政编码],[用户ID]) 值(@0、@1、@2、@3) 选择[ID] 来自[dbo]。[地址] 其中@ROWCOUNT>0和[ID]=scope_identity()',N'@0 nvarchar(max),@1 nvarchar(max),@2 nvarchar(max),@3 int',@0=N'City',@1=N'Street',@2=N'Postcode',@3=1 如何避免第二个SQL?


  • 您是否尝试过稍微更改类定义:

    exec sp_executesql N'INSERT [dbo].[Addresses]([City], [Street], [Postcode], [User_ID]) VALUES (@0, @1, @2, @3) SELECT [ID] FROM [dbo].[Addresses] WHERE @@ROWCOUNT > 0 AND [ID] = scope_identity()',N'@0 nvarchar(max) ,@1 nvarchar(max) ,@2 nvarchar(max) ,@3 int',@0=N'City',@1=N'Street',@2=N'Postcode',@3=1 现在你可以写:

    public class Address
    {
        public int ID { get; set; }
    
        public string City { get; set; }
    
        public string Street { get; set; }
    
        public string Postcode { get; set; }
    
        public virtual User User { get; set;}
    }
    

    当您访问属性时,
    Addresses
    nav属性是延迟加载的(即,
    user.Addresses
    ),这就是您获得第二个SQL命令的原因

    尝试禁用延迟加载并查看其是否有效(不要忘记在
    用户的构造函数中初始化
    地址
    属性,例如:

    var context = new TestDbContext();
    var user = context.Users.FirstOrDefault(item => item.ID == 1);
    
    context.Addresses.Add(new Address()
    {
        City = "City",
        Street = "Street",
        Postcode = "Postcode",
        User = user
    });
    
    context.SaveChanges();
    
    公共用户()
    {
    地址=新的HashSet();
    }
    
    正如前面所指出的,这里的问题是您的
    地址
    属性是一个导航属性,因此当您访问它时,EF会生成一个
    选择
    语句来加载集合。为避免发生这种情况,您有两个选项:

  • 当您加载
    用户时,请立即加载地址,这样您在第一次加载用户时就会受到影响,例如
    用户。包括(x=>x.addresses)
  • 通过使
    地址
    属性为非虚拟属性,禁用该特定属性上的延迟加载

  • 您甚至可以阻止前两个查询

    您已经知道用户的ID值,因此只需在
    Address
    中设置外键值即可。当然,
    Address
    应该具有以下属性:

    public User()
    {
        Addresses = new HashSet<Address>();
    }
    

    这对
    User
    UserID
    被称为a,这是处理EF中关联的首选方法(正是因为它可以减少查询数量)。

    我会在
    Address
    类中添加一个
    UserID
    外键,然后我会这样做:

    public class Address
    {
        public int ID { get; set; }
        public string City { get; set; }
        public string Street { get; set; }
        public string Postcode { get; set; }
    
        public int UserID { get; set; } // Set this property
        public User User { get; set; }
    }
    
    无需检索用户或用户的现有地址

    外键使实体框架更易于使用:

    关系修复将同步导航属性:

    public User()
    {
        Addresses = new HashSet<Address>();
    }
    

    var context = new TestDbContext();
    
    context.Addresses.Add(new Address()
    {
        UserId = 1,
        City = "City",
        Street = "Street",
        Postcode = "Postcode",
    });
    
    context.SaveChanges();