Tyler Daniels
Tyler Daniels

Reputation: 633

How to Abstract Creation of Singleton Using XML Serialization

I'm trying to create a generic repository using the Singleton pattern which persists to an XML file. There are currently 3 concrete repositories, each of which are loaded from different XML files. I cannot figure out how to properly abstract the creation of the repository from the XML file. Here are my classes for one implementation of this repository.

Models

public interface IEntity
{
    string Name { get; }
}

public class Game : IEntity
{
    [XmlElement("Name")]
    public string Name { get; set; }

    [XmlElement("ExecutablePath")]
    public string ExecutablePath { get; set; }

    private Game() { } // Required for XML serialization.

    public Game(string name, string executablePath)
    {
        Name = name;
        ExecutablePath = executablePath;
    }
}

Repository

public interface IRepository<TEntity> where TEntity: IEntity
{
    List<TEntity> Items { get; }

    void Add(TEntity entity);
    void Delete(TEntity entity);
}

public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : IEntity 
{
    public abstract List<TEntity> Items { get; set; }

    private static Repository<TEntity> _instance;
    public static Repository<TEntity> Instance
    {
        get
        {
            if (_instance == null)\
                _instance = RepositoryFactory<Repository<TEntity>>.LoadFromFile();

            return _instance;
        }
    }

    public void Add(TEntity entity)
    {
        Items.Add(entity);
    }

    public void Delete(TEntity entity)
    {
        Items.Remove(entity);
    }
}

public static class RepositoryFactory<TRepository> 
{
    public static TRepository LoadFromFile() 
    {
        using (TextReader reader = new StreamReader("Games.xml"))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(TRepository));
            return (TRepository)serializer.Deserialize(reader);
        }
    }
}

[XmlRoot("Games")]
public class GameRepository : Repository<Game>
{
    [XmlElement("Game")]
    public override sealed List<Game> Items { get; set; }

    private GameRepository()
    {
        Items = new List<Game>();
    }
}

When trying to call 'GameRepository.Instance', I get this very generic exception: {"<Games xmlns=''> was not expected."}

This is admittedly one of my first times trying to gel these design patterns (singleton/factory) together, so this approach may have been completely wrong from the start. I can't diagnose exactly what is wrong from the exception (inner exception is null), so I'm hoping someone who knows these patterns can help me out.

UPDATE

Sample Games.xml File

<?xml version="1.0" encoding="utf-8" ?>
<Games>
  <Game>Test</Game>
  <ExecutablePath>ExecutablePath</ExecutablePath>
</Games>

Sample Code - Still Failing

class Program
{
    static void Main(string[] args)
    {
        IRepository<Game> gameRepository = new XmlRepository<Game>();
        Test test = new Test(gameRepository);

        Console.WriteLine(test.GetFirstGame());
        Console.Read();
    }
}
class Test
{
    private IRepository<Game> GameRepository { get; set; }

    public Test(IRepository<Game> gameRepository)
    {
        GameRepository = gameRepository;
    }

    public string GetFirstGame()
    {
        return GameRepository.Items.Value.FirstOrDefault().Name;
    }
}

Upvotes: 1

Views: 620

Answers (2)

Matt Cole
Matt Cole

Reputation: 2601

I think that GameRepository is redundant here. You can serialize/deserialize the list of games directly to the xml file (see below). As for the overall design, it seems you have gone a little bit singleton crazy (doing too much in a static context). This makes the code hard to test and change. I would recommend reading up on dependency injection, and using an IoC container to manage the lifetimes of objects (including singletons). I was able to get your example working with some modifications to the read/write code, and have changed the design to remove any concerns about singletons from the concrete implementations. In this way client code can choose to use a singleton or not. Hope this helps.

public class XmlRepository<TEntity> : IRepository<TEntity> where TEntity : IEntity
{
    private readonly string _filePath;
    private readonly Lazy<List<TEntity>> _items;

    public XmlRepository(string filePath)
    {
        _filePath = filePath;
        _items = new Lazy<List<TEntity>>(Load);
    }

    public IEnumerable<TEntity> Items
    {
        get { return _items.Value; }
    }

    public void Add(TEntity item)
    {
        if (_items.Value.Contains(item))
            throw new InvalidOperationException();

        _items.Value.Add(item);
    }

    public void Delete(TEntity item)
    {
        _items.Value.Remove(item);
    }

    public void Save()
    {
        var serializer = new XmlSerializer(typeof(List<TEntity>));
        using (var reader = File.CreateText(_filePath))
        {
            serializer.Serialize(reader, _items.Value);
        }
    }

    private List<TEntity> Load()
    {
        if (!File.Exists(_filePath))
            return new List<TEntity>();

        var serializer = new XmlSerializer(typeof(List<TEntity>));
        using (var reader = File.OpenText(_filePath))
        {
            return (List<TEntity>)serializer.Deserialize(reader);
        }
    }
}

public static class RepositorySingletons
{
    private static IRepository<Game> _gameRepository; 

    public static IRepository<Game> GameRepository
    {
        get
        {
            return _gameRepository ??
                (_gameRepository = new XmlRepository<Game>("Game.xml"));
        }
    }
}

public class MyGamesApplication
{
    private readonly IRepository<Game> _gameRepository;

    public MyGamesApplication(IRepository<Game> gameRepository)
    {
        // I don't care if I have a singleton or not :)
        _gameRepository = gameRepository;
    }

    public MyGamesApplication()
    {
        // I need to fetch a singleton :(
        _gameRepository = RepositorySingletons.GameRepository;
    }

    public void Run()
    {
        var game = new Game
        {
            Id = 55378008,
            Title = "Abe's Oddysee"
        };
        _gameRepository.Add(game);
        Console.WriteLine("Added game " + game.Title);

        _gameRepository.Save();
        Console.WriteLine("Saved games");
    }
}

Upvotes: 2

Tara
Tara

Reputation: 615

Your RepositoryFactory tries to deserialize the file using the type TRepository -- right now, your GameRepository passes Game as TRepository so it's trying to deserialize an object of type Game -- of course, that's not what you've serialized.

You've serialized an object of type GameRepository and are trying to deserialize it as an object of type Game.

Try this:

[XmlRoot("Games")]
public class GameRepository : Repository<GameRepository>
{
    [XmlElement("Game")]
    public override sealed List<Game> Items { get; set; }

    private GameRepository()
    {
        Items = new List<Game>();
    }
}

Edit: Upon reviewing, that answer was mostly wrong.

It is true that RepositoryFactory is using the wrong type to deserialize, but it's not getting Game as the type, it's getting Repository<Game> as the type, which is different from GameRepository. The serializer does not handle inheritance at all - it needs to know the specific type it is deserializing.

I haven't thought of a good solution to this, but I'll keep thinking.

Upvotes: 1

Related Questions