Когда я извлекаю список элементов из базы данных, включая некоторых дочерних элементов (через .Include), и произвольно заказываю, EF дает мне неожиданный результат. Я создаю/клонирует дополнительные элементы.
Чтобы лучше объяснить себя, я создал небольшой и простой проект EF CodeFirst, чтобы воспроизвести проблему. Сначала я дам вам код для этого проекта.
Проект
Создайте базовый проект MVC3 и добавьте пакет EntityFramework.SqlServerCompact через Nuget.
Это добавляет последние версии следующих пакетов:
- 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>
Это должно быть все, и ваше приложение должно скомпилировать:)
Проблема
Как вы можете видеть, у нас есть две простые модели (Person and Address), а Person может иметь несколько адресов.
Мы засеваем сгенерированную базу данных 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)
из запроса, все будет хорошо. но, конечно, адреса не загружаются, и доступ к этой коллекции будет каждый раз вызывать новый вызов в базе данных. - Я могу сначала получить данные из базы данных и рандомизировать позже, просто добавив .ToList() перед .OrderBy.. вот так:
var persons = db.Persons.Include(p => p.Addresses).ToList().OrderBy(p => Guid.NewGuid());
Кто-нибудь знает, почему так происходит?
Может ли это быть ошибкой в генерации SQL?