Reputation: 633
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
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
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