tjugg tjugg - 30 days ago 9
C# Question

Using interfaces in models with SQLite

Let's say I have an interface like this:

public interface IUser
int Id { get; }

string Name { get; }

List<IMonthlyBudget> MonthlyBudget { get; }

and then I have a model that implements this:

public class User : IUser
public int Id { get; set; }

public string Name { get; set; }

public List<IMonthlyBudget> MonthlyBudget { get; set; }

and here I have the IMonthlyBudget:

public interface IMonthlyBudget
int Id { get; }

float MonthlyMax { get; }

float CurrentSpending { get; }

float MonthlyIncome { get; }

Now I have my models. But the issue comes with using SQLite. SQLite can't understand what is the real implementation of IMonthlyBudget. I understand why, but I really don't want remove the interface and expose the real implementation to all the clients that use these models. In my project structure I have a Core project that has all the model interfaces, and the model implementation are in a data access project.

Is there something wrong with how I'm approaching this problem? I assume i'm not the first one to run into a issue like this. Isn't it completely normal practice to keep model interfaces (what repositories etc then use as their return types, parameters and stuff like that) and implement the actual concrete models in a data access project?

And can someone explain why I can't do this:

public class User : IUser
public int Id { get; set; }

public string Name { get; set; }

public List<MonthlyBudget> MonthlyBudget { get; set; }

MonthlyBudget implements IMonthlyBudget, shouldn't it be completely fine to use the concrete model as the type instead of the the interface when the concrete model actually implements the interface?

Answer Source

A few questions here, so I'll break it down into sections:

Use of Interfaces

It is definitely good practice to interface classes that perform operations. For example, you may have a data service (i.e. data access layer) interface that allows you to do operations to read and modify data in your persistent store. However, you may have several implementations of that data service. One implementation may save to the file system, another to a DBMS, another is a mock for unit testing, etc.

However, in many cases you do not need to interface your model classes. If you're using an anemic business object approach (as opposed to rich business objects), then model classes in general should just be containers for data, or Plain Old CLR Objects (POCO). Meaning these objects don't have any real functionality to speak of and they don't reference any special libraries or classes. The only "functionality" I would put in a POCO is one that is dependent only upon itself. For example, if you have a User object that has a FirstName and LastName property, you could create a read-only property called FullName that returns a concatenation of the two. POCOs are agnostic as to how they are populated and therefore can be utilized in any implementation of your data service.

This should be your default direction when using an anemic business object approach, but there is at least one exception I can think of where you may want to interface your models. You may want to support for example a SQLite data service, and a Realm (NoSQL) data service. Realm objects happen to require your models to derive from RealmObject. So, if you wanted to switch your data access layer between SQLite and Realm then you would have to interface your models as you are doing. I'm just using Realm as an example, but this would also hold true if you wanted to utilize your models across other platforms, like creating an observable base class in a UWP app for example.

The key litmus test to determining whether you should create interfaces for your models is to ask yourself this question:

"Will I need to consume these models in various consumers and will those consumers require me to define a specific base class for my models to work properly in those consumers?"

If the answer to this is "yes", then you should make interfaces for your models. If the answer is "no", then creating model interfaces is extraneous work and you can forego it and let your data service implementations deal with the specifics of their underlying data stores.

SQLite Issue

Whether you continue to use model interfaces or not, you should still have a data access implementation for SQLite which knows that it's dealing with SQLite-specific models and then you can do all your CRUD operations directly on those specific implementations of your model. Then since you're referring to a specific model implementation, SQLite should work as usual.

Type Compatibility

To answer your final question the type system does not see this...

List<IMonthlyBudget> MonthlyBudget

as being type-compatible with this...

List<MonthlyBudget> MonthlyBudget

In our minds it seems like if I have a list of apples, then it should be type-compatible with a list of fruit. The compiler sees an apple as a type of fruit, but not a list of apples as a type of a list of fruit. So you can't cast between them like this...

List<IMonthlyBudget> myMonthlyBudget = (List<IMonthlyBudget>) new List<MonthlyBudget>();

but you CAN add a MonthlyBudget object to a list of IMonthlyBudget objects like this...

List<IMonthlyBudget> myMonthlyBudget = new List<IMonthlyBudget>();
myMonthlyBudget.Add(new MonthlyBudget());

Also you can use the LINQ .Cast() method if you want to cast an entire list at once.

The reason behind this has to do with type variance. There's a good article on it here that can shed some light as to why:

Covariance and Contravariance

I hope that helps! :-)