Как создать поддельный репозиторий с ассоциацией «1 ко многим» для MVC

Я пытаюсь создать поддельный репозиторий для модульного тестирования с объектом класса, который имеет отношение «один ко многим». Я использую ASP.NET MVC и Linq to SQL. Я ссылаюсь на книгу Стивена Сандерсона "Pro ASP.NET MVC Framework".

Я создал классы сущностей с ассоциацией:

[Table(Name = "Albums")]
public class Album
{
    [Column(IsDbGenerated = true, IsPrimaryKey = true)]
    public int AlbumID { get; set; }
    [Column]
    public string Title { get; set; }

    private EntitySet<Track> _tracks;

    [Association(Storage = "_tracks", ThisKey = "AlbumID", OtherKey = "AlbumID")]
    public EntitySet<Track> Tracks
    {
        get { return _tracks; }
        set { _tracks.Assign(value); }
    }
}

[Table(Name = "Tracks")]
public class Track
{
    [Column(IsPrimaryKey = true)]
    public int TrackID { get; set; }
    [Column]
    public string Title { get; set; }
    [Column]
    public int AlbumID { get; set; }
}

И абстрактный интерфейс для репозитория:

public interface IMusicRepository
{
    IQueryable<Album> Albums { get; }
    IQueryable<Track> Tracks { get; }
}

Мне удалось создать поддельный репозиторий для альбомов — FakeMusicRepository:

public class FakeMusicRepository : IMusicRepository
{

    private static IEnumerable<Track> fakeTracks = new List<Track>
    {
        new Track {AlbumID = 1, TrackID = 1, Title = "Flood"},
        new Track {AlbumID = 1, TrackID = 2, Title = "Liquid"},
        new Track {AlbumID = 2, TrackID = 1, Title = "Song 1"},
        new Track {AlbumID = 2, TrackID = 2, Title = "Song 2"}
    }.AsEnumerable();

    private static IQueryable<Album> fakeAlbums = new List<Album>
    {
          new Album {AlbumID = 1, Title = "Jars of Clay"},
          new Album {AlbumID = 2, Title = "Wind in the Wheat"}
    }.AsQueryable();


    public IQueryable<Album> Albums
    {
        get { return fakeAlbums; }
    }

    public IQueryable<Track> Tracks
    {
        get { return fakeTracks.AsQueryable(); }
    }

}

Который отлично работает, пока я не пытаюсь получить доступ к каким-либо трекам. Другими словами, Album.Title работает, но при доступе к Album.Tracks.Count() генерируется исключение null Reference. Я не был уверен, будут ли Linq-To-SQL или Linq-To-Objects подхватывать ассоциацию и использовать ее автоматически с поддельными объектами или нет.

Проблема в том, что независимо от того, что я пытаюсь, я не могу назначить класс Tracks to Album. Я знаю, что EntitySet основан на IEnumerable, но было непросто преобразовать список Enumerable в EntitySet.

Альбом ожидает EntitySet, но единственный способ, которым я смог его передать, - это пользовательский помощник:

public static class EntityCollectionHelper
{
    public static EntitySet<T> ToEntitySet<T>(this IEnumerable<T> source) where T : class
    {
        EntitySet<T> set = new EntitySet<T>();
        set.AddRange(source);
        return set;
    }
}

Это так близко, как я пришел:

public class FakeMusicRepository2 : IMusicRepository
{
    private static IEnumerable<Track> fakeTracks = new List<Track>
    {
        new Track {AlbumID = 1, TrackID = 1, Title = "Flood"},
        new Track {AlbumID = 1, TrackID = 2, Title = "Liquid"},
        new Track {AlbumID = 2, TrackID = 1, Title = "Song 1"},
        new Track {AlbumID = 2, TrackID = 2, Title = "Song 2"}
    }.AsEnumerable();

    private static EntitySet<Track> Tracks1 = (from t in fakeTracks where t.AlbumID == 1 select t).ToEntitySet();
    private static EntitySet<Track> Tracks2 = (from t in fakeTracks where t.AlbumID == 2 select t).ToEntitySet();

    private static IQueryable<Album> fakeAlbums = new List<Album>
    {
          new Album {AlbumID = 1, Title = "Jars of Clay", Tracks=Tracks1},
          new Album {AlbumID = 2, Title = "Wind in the Wheat", Tracks=Tracks2}
    }.AsQueryable();


    public IQueryable<Album> Albums
    {
        get { return fakeAlbums; }
    }

    public IQueryable<Track> Tracks
    {
        get { return fakeTracks.AsQueryable(); }
    }

}

Однако, когда я пытаюсь получить доступ к Album.Tracks, я получаю

System.NullReferenceException : ссылка на объект не указывает на экземпляр объекта.

Вот импламентация/модульный тест:

    [Test]
    public void Test_FakeMusicRepository()
    {
        // Arrange: Setup Repository
        var dc = new FakeMusicRepository2();

        // Act:
        var albums = dc.Albums.AsQueryable();
        // Load the first test location
        Album album = dc.Albums.First();

        // Assert: That there are records in the Album Object
        Assert.Greater(albums.Count(), 0, "No Albums Records Returned!");

        //Assert that our first Album is not Null
        Assert.IsNotNull(album, "returned Album is Null!");
        // Assert: That we have the correct Album
        Assert.AreEqual(album.AlbumID, 1, "Returned Album ID is not correct!");

        // Try to Count the related sub table (Tracks)
        var trackCount = album.Tracks.Count();

        // Try to get the first Track
        var track1 = album.Tracks.FirstOrDefault();


        // Assert: The Album has Tracks 
        Assert.Greater(trackCount, 0, "Album has no Tracks");

        // Assert: That First track is not Null
        Assert.IsNotNull(track1, "Track1 object was Null!");

        // Assert: That Track1 has data
        Assert.IsNotNull(track1.TrackID, "Track1's ID was Null!");
    }

Я потратил несколько дней на поиск ответа на этот вопрос и не нашел ни одного примера, поэтому, если кто-то сможет опубликовать пример рабочего ответа, я уверен, что другим это тоже будет интересно.

Я очень новичок в MVC и модульном тестировании, поэтому, пожалуйста, будьте со мной полегче. У меня также есть IoC и Moq, но я знаю о них еще меньше, но я планирую использовать оба в этом проекте. Если есть лучший способ сделать это, то я весь внимание! Спасибо.


person ColinICN    schedule 01.03.2010    source источник
comment
У вас есть код, который вы используете для доступа к трекам? т.е. ваш код реализации? Это может помочь отладке. Если вы делаете Album.Tracks.Count(), причина может заключаться в том, что вы неправильно назначаете дорожки, но это зависит от кода реализации.   -  person Odd    schedule 02.03.2010
comment
Хорошая идея - я разместил код модульного теста/реализации.   -  person ColinICN    schedule 03.03.2010


Ответы (2)


Класс Album кажется немного странным, особенно эта часть:

private EntitySet<Track> _tracks;

[Association(Storage = "_tracks", ThisKey = "AlbumID", OtherKey = "AlbumID")]
public EntitySet<Track> Tracks
{
    get { return _tracks; }
    set { _tracks.Assign(value); }
}

Частному члену _tracks никогда не присваивается значение, и вы вызываете для него метод Assign в установщике, который генерирует исключение. Кстати, компилятор должен был вас предупредить. Вы можете попробовать указать значение по умолчанию или проверить значение null в установщике перед вызовом метода Assign:

private EntitySet<Track> _tracks = new EntitySet<Track>();

Кроме того, ваш метод расширения для преобразования IEnumerable<T> в EntitySet<T> кажется хорошим.

И, наконец, когда вы назначаете поддельные альбомы, вам нужно установить треки:

private static IQueryable<Album> fakeAlbums = new List<Album>
{
      new Album 
      {
          AlbumID = 1, 
          Title = "Jars of Clay", 
          Tracks = fakeTracks.ToEntitySet() 
      },
      new Album 
      {
          AlbumID = 2, 
          Title = "Wind in the Wheat",
          Tracks = fakeTracks.ToEntitySet() 
      }
}.AsQueryable();
person Darin Dimitrov    schedule 02.03.2010
comment
Спасибо Даривн - добавление _tracks = new EntitySet‹Track›(); решил это. Как вы сказали, я не присваивал значение _tracks. Примеры MSDN и книги, которые у меня есть, показывают код таким образом без назначения - я думаю, что Linq To Sql позаботится об этом за вас. Да, компилятор выдавал предупреждения, но поскольку Linq to SQL работал, я решил, что все в порядке. Вы правы в том, что забыли назначить треки в объекте «Альбом». Я пропустил вставку блока кода, который показывает это. Я добавил недостающий блок кода, а также добавлю свой код реализации модульного теста. Спасибо! - person ColinICN; 03.03.2010

Вот исправленный (рабочий) класс альбома:

[Table(Name = "Albums")]
public class Album
{
    [Column(IsDbGenerated = true, IsPrimaryKey = true)]
    public int AlbumID { get; set; }
    [Column]
    public string Title { get; set; }

    private EntitySet<Track> _tracks = new EntitySet<Track>();

    [Association(Storage = "_tracks", ThisKey = "AlbumID", OtherKey = "AlbumID")]
    public EntitySet<Track> Tracks
    {
        get { return _tracks; }
        set { _tracks.Assign(value); }
    }
}

[Table(Name = "Tracks")]
public class Track
{
    [Column(IsPrimaryKey = true)]
    public int TrackID { get; set; }
    [Column]
    public string Title { get; set; }
    [Column]
    public int AlbumID { get; set; }
}

Проблема заключалась в том, что я не создавал экземпляр объекта «Альбом», как указал Дарин. Модульный тест теперь работает корректно. Я тестировал как поддельный репозиторий, так и живые данные, и все работает! Странно, что примеры MSDN (http://msdn.microsoft.com/en-us/library/bb425822.aspx) также не отображало назначение.

person ColinICN    schedule 03.03.2010