asp.net-mvc – 实体框架包含OrderBy随机生成重复数据

前端之家收集整理的这篇文章主要介绍了asp.net-mvc – 实体框架包含OrderBy随机生成重复数据前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
当我从包含一些孩子(通过.Include)的数据库中检索到一个项目列表,并且随机订购时,EF给我一个意想不到的结果..我创建/克隆添加项目..

为了更好地解释自己,我创建了一个简单的EF CodeFirst项目来重现问题。
首先我会给你这个项目的代码

该项目

创建一个基本的MVC3项目,并通过Nuget添加EntityFramework.sqlServerCompact包。
增加了以下软件包的最新版本:

> EntityFramework v4.3.0
> sqlServerCompact v4.0.8482.1
> EntityFramework.sqlServerCompact v4.1.8482.2
> WebActivator v1.5

模型和DbContext

using System.Collections.Generic;
using System.Data.Entity;

namespace RandomWithInclude.Models
{
    public class PeopleContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }
        public DbSet<Address> Addresses { get; set; }
    }

    public class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Address> Addresses { get; set; }
    }

    public class Address
    {
        public int ID { get; set; }
        public string AdressLine { get; set; }

        public virtual Person Person { get; set; }
    }
}

数据库设置和种子数据:EF.sqlServerCompact.cs

using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using RandomWithInclude.Models;

[assembly: WebActivator.PreApplicationStartMethod(typeof(RandomWithInclude.App_Start.EF),"Start")]

namespace RandomWithInclude.App_Start
{
    public static class EF
    {
        public static void Start()
        {
            Database.DefaultConnectionFactory = new sqlCeConnectionFactory("System.Data.sqlServerCe.4.0");
            Database.SetInitializer(new DbInitializer());
        }
    }
    public class DbInitializer : DropCreateDatabaseAlways<PeopleContext>
    {
        protected override void Seed(PeopleContext context)
        {
            var address1 = new Address {AdressLine = "Street 1,City 1"};
            var address2 = new Address {AdressLine = "Street 2,City 2"};
            var address3 = new Address {AdressLine = "Street 3,City 3"};
            var address4 = new Address {AdressLine = "Street 4,City 4"};
            var address5 = new Address {AdressLine = "Street 5,City 5"};
            context.Addresses.Add(address1);
            context.Addresses.Add(address2);
            context.Addresses.Add(address3);
            context.Addresses.Add(address4);
            context.Addresses.Add(address5);
            var person1 = new Person {Name = "Person 1",Addresses = new List<Address> {address1,address2}};
            var person2 = new Person {Name = "Person 2",Addresses = new List<Address> {address3}};
            var person3 = new Person {Name = "Person 3",Addresses = new List<Address> {address4,address5}};
            context.Persons.Add(person1);
            context.Persons.Add(person2);
            context.Persons.Add(person3);
        }
    }
}

控制器:HomeController.cs

using System;
using System.Data.Entity;
using System.Linq;
using System.Web.Mvc;
using RandomWithInclude.Models;

namespace RandomWithInclude.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var db = new PeopleContext();
            var persons = db.Persons
                                .Include(p => p.Addresses)
                                .OrderBy(p => Guid.NewGuid());

            return View(persons.ToList());
        }
    }
}

视图:Index.cshtml

@using RandomWithInclude.Models
@model IList<Person>

<ul>
    @foreach (var person in Model)
    {
        <li>
            @person.Name
        </li>
    }
</ul>

这应该是全部,你的应用程序应该编译:)

问题

你可以看到,我们有2个简单的模型(个人和地址),而人可以有多个地址。
我们种下生成数据库3人和5地址。
如果我们从数据库获取所有的人员,包括地址和随机化的结果,并打印出这些人的名字,那就是错误的地方。

结果,我有时会得到4个人,有时5个,有时3个,我期望3.总是。
例如。:

>人1
>人3
>人1
>人3
人2

所以..它的复制/克隆数据!这不是很酷..
看来EF似乎没有跟踪哪个地址是哪个人的孩子。

生成SQL查询是这样的:

SELECT 
    [Project1].[ID] AS [ID],[Project1].[Name] AS [Name],[Project1].[C2] AS [C1],[Project1].[ID1] AS [ID1],[Project1].[AdressLine] AS [AdressLine],[Project1].[Person_ID] AS [Person_ID]
FROM ( SELECT 
    NEWID() AS [C1],[Extent1].[ID] AS [ID],[Extent1].[Name] AS [Name],[Extent2].[ID] AS [ID1],[Extent2].[AdressLine] AS [AdressLine],[Extent2].[Person_ID] AS [Person_ID],CASE WHEN ([Extent2].[ID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
    FROM  [People] AS [Extent1]
    LEFT OUTER JOIN [Addresses] AS [Extent2] ON [Extent1].[ID] = [Extent2].[Person_ID]
)  AS [Project1]
ORDER BY [Project1].[C1] ASC,[Project1].[ID] ASC,[Project1].[C2] ASC

解决方法

>如果我从查询删除.Include(p => p.Addresses),一切顺利。但是当然地址没有加载,并且访问该集合将每次都对数据库进行新的调用
>我可以先从数据库获取数据,然后通过在.OrderBy之前添加一个.ToList()来随机化。如下所示:var persons = db.Persons.Include(p => p.Addresses).ToList( ).OrderBy(p => Guid.NewGuid());

有没有人知道为什么会这样发生?
这可能是sql生成中的错误吗?

解决方法

可以通过阅读 AakashM answerNicolae Dascalu answer来排序,似乎Linq OrderBy需要一个稳定的排名功能,NewID / Guid.NewGuid不是。

所以我们必须使用一个在单个查询中稳定的随机生成器。

为了实现这一点,在每次查询之前使用.Net随机生成器来获取随机数。然后将该随机数与实体的唯一属性相结合,以便随机排序。为了’随机化’一些结果,校验和。 (校验和是计算哈希的sql Server函数;创建于this blog的原始构想)

假设Person Id是一个int,你可以这样写你的查询

var rnd = (new Random()).NextDouble();
var persons = db.Persons
    .Include(p => p.Addresses)
    .OrderBy(p => sqlFunctions.Checksum(p.Id * rnd))
    // Uniqueness of ordering ranking must be ensured.
    .ThenBy(p => p.Id);

像NewGuid黑客一样,这很可能不是一个很好的随机发生器,具有良好的分布等等。但它并不会导致实体在结果中重复。

编辑:如果您的查询排序不保证您的实体排名的唯一性,您必须补充它以保证它,因此我添加了ThenBy。
如果您的查询根实体的排名不是唯一的,其包含的儿童可能会与具有相同排名的其他实体的孩子混合。然后这个bug会留在这里。

旁注:我宁愿使用.Next()方法获取一个int,然后将它通过一个xor(^)组合到一个实体int unique属性,而不是使用double和乘法。但是sqlFunctions.Checksum不幸的是不提供int数据类型的重载,尽管sql Server函数应该支持它。你可以用演员克服这个,但是为了保持简单,我终于选择了乘法。

猜你在找的asp.Net相关文章