为什么Entity Framework 5在同一实体上执行.ToList()与.Count()时会查询不同的表?

qua*_*els 6 linq-to-entities entity-framework entity-framework-5

我正在使用Entity Framework使用Entity Splitting将两个表映射到一起,如此此处所述.

我发现如果我执行一个.ToList()on IQueryable<SplitEntity>然后结果来自内部连接.但是,如果我使用相同的IQueryable并执行.Count()它将返回完全连接返回的记录数.

这是一个失败的单元测试:

    [TestMethod]
    public void GetCustomerListTest()
    {
        // arrange
        List<Customer> results;
        int count;

        // act
        using (var context = new DataContext())
        {
            results = context.Customers.ToList();
            count = context.Customers.Count();
        }

        // assert
        Assert.IsNotNull(results); // succeeds
        Assert.IsTrue(results.Count > 0); // succeeds. Has correct records from inner join
        Assert.AreEqual(count, results.Count); // This line fails. Has incorrect count from full join.
    }
Run Code Online (Sandbox Code Playgroud)

这让我觉得非常糟糕.我怎样才能让.Count()方法从内部连接返回结果.ToList()

更新 - SQL

我对完全与内部连接错了.

.ToList()导致:

    SELECT 
    [Extent1].[CustomerNumber] AS [CustomerNumber], 
    -- ...etc...
    [Extent2].[CustomerName] AS [CustomerName], 
    -- ... etc...
    FROM  [dbo].[CustomerTable1] AS [Extent1]
    INNER JOIN [dbo].[CustomerTable2] AS [Extent2] ON [Extent1].[CustomerNumber] = [Extent2].[CustomerNumber]
Run Code Online (Sandbox Code Playgroud)

.Count()导致:

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[customerTable2] AS [Extent1]
)  AS [GroupBy1]
Run Code Online (Sandbox Code Playgroud)

更新 - DataContext和实体代码

DataContext:

public class DataContext : DbContext
    {
        public DataContext() { Database.SetInitializer<DataContext>(null); }

        public DbSet<Customer> Customers { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Configurations.Add(new CustomerMapping());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

客户映射(FluentAPI):

public class CustomerMapping : EntityTypeConfiguration<Customer>
{
    public CustomerMapping()
    {
        this.Map( m => {
                    m.Properties( x => new { x.CustomerNumber, /*...etc...*/});
                    m.ToTable("CustomerTable1");
                })
        .Map( m => {
                    m.Properties( x => new { x.CustomerName, /*...etc...*/});
                    m.ToTable("CustomerTable2");
                });
    }
}
Run Code Online (Sandbox Code Playgroud)

客户实体:

public class Customer
{
    [Key]
    public string CustomerNumber { get; set; }

    public string CustomerName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

Sla*_*uma 5

如果数据库以及Entity Framework中创建的所有记录CustomerTable1和应用程序代码中的调用都不会发生这种差异,您可以直接将其报告为错误.CustomerTable2SaveChanges

如果要映射到现有数据库,或者其他应用程序将记录写入表中,并且实际上您希望不是每个记录CustomerTable1都有相应的记录,CustomerTable2反之亦然,那么Entity Splitting是您的数据库模式的错误映射.

很明显的区别意味着你可以拥有Customers的一个CustomerNumber(等),但没有一个CustomerName(等) -或者反过来.对此进行建模的更好方法是一对一关系,其中一方是必需的,另一方是可选的.您将需要一个额外的实体和导航属性,例如:

[Table("CustomerTable1")]
public class Customer
{
    [Key]
    public string CustomerNumber { get; set; }
    // + other properties belonging to CustomerTable1

    public AdditionalCustomerData AdditionalCustomerData { get; set; }
}

[Table("CustomerTable2")]
public class AdditionalCustomerData
{
    [Key]
    public string CustomerNumber { get; set; }
    public string CustomerName { get; set; }
    // + other properties belonging to CustomerTable2
}
Run Code Online (Sandbox Code Playgroud)

使用此Fluent API映射:

public class CustomerMapping : EntityTypeConfiguration<Customer>
{
    public CustomerMapping()
    {
        this.HasOptional(c => c.AdditionalCustomerData)
            .WithRequired()
            .WillCascadeOnDelete(true);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @quakkels:我理解你的论点和抱怨.另一方面,只对其中一个表使用`Count`查询是一种性能优化,因为在假设*下,JOIN不是必需的*2表中的记录是严格的一对一关系我认为,这是EF在使用Entity Splitting时所做的假设.另外,我认为,INNER JOIN的使用是基于相同假设的决定.它的目的不是过滤掉*两个*表中没有数据的客户.我会考虑在CodePlex上报告,看看EF团队说了些什么. (3认同)