I have a program that performs database operations using SQLite.SQLiteAsyncConnection. I have two different models I'd like to work with, say Customers and Cars. Customers could be defined as
public class Customer
{
[PrimaryKey]
public string Id {get; set;}
public string Name {get; set;}
public int NumberOfKids {get; set;}
public decimal Budget {get; set;}
}
and Cars could be defined as
public class Car
{
[PrimaryKey]
public string Id {get; set;}
public int Year {get; set;}
public string Make {get; set;}
public string Model {get; set;}
public int Mileage {get; set;}
}
Note that the models have different numbers and types of members, but both use a string Id as the primary key. Database operations are defined in a class that I inherit from a public interface IDataStore which declares operations for adding, updating, deleting, and getting items:
public interface IDataStore<T>
{
Task<bool> AddItemAsync(T item);
Task<bool> UpdateItemAsync(T item);
Task<bool> DeleteItemAsync(T item);
Task<T> GetItemAsync(string id);
Task<IEnumerable<T>> GetItemsAsync(bool forceRefresh = false);
}
Do I have to define a separate DataStore class, or separate overloaded functions, for Cars and Customers, even though these operations are exactly the same for both models except for the data types they take and use as parameters? What I'd like to be able to declare is something like
public class SQLiteItemDataStore : IDataStore<var>
{
...
public async Task<var> GetItemAsync(string Id)
{
return await DrinkDatabase.Table<var>().Where(i => i.Id == id).FirstOrDefaultAsync();
}
...
}
But of course this creates a compiler error since you can only use "var" in a variable declaration, so I instead have to create one class for Cars
public class SQLiteItemDataStore : IDataStore<Car>
{
...
public async Task<Car> GetItemAsync(string Id)
{
return await DrinkDatabase.Table<Car>().Where(i => i.Id == id).FirstOrDefaultAsync();
}
...
}
and another for Customers
public class SQLiteItemDataStore : IDataStore<Customer>
{
...
public async Task<Customer> GetItemAsync(string Id)
{
return await DrinkDatabase.Table<Customer>().Where(i => i.Id == id).FirstOrDefaultAsync();
}
...
}
Is there some standard way to avoid having to repeat the database operation functions for every model type I want to use with a database?
I think you are looking for the following solution:
public interface IDataStore
{
Task<bool> AddItemAsync<T>(T item);
Task<bool> UpdateItemAsync<T>(T item);
Task<bool> DeleteItemAsync<T>(T item);
Task<T> GetItemAsync(string id);
Task<IEnumerable<T>> GetItemsAsync(bool forceRefresh = false);
}
public class SQLiteDataStore
{
...
public async Task<T> GetItemAsync<T>(string Id)
{
return await DrinkDatabase.Table<T>().Where(i => i.Id == id).FirstOrDefaultAsync();
}
...
}
but this is not the commonly used approach, moreover it won't work because you can't know that T has Id property.
Try this:
you should define a base entity class and let all your model inherit from it
public abstract class Entity {
public abstract string Id {get;set;}
}
public class Car : Entity
{
//all others cars properties
}
public class Customer : Entity
{
//all others customers properties
}
you should define a common generic repository interface (like you did with IDataStore< T >)
public interface IRepository<T> where T : Entity // T must inherit from Entity
{
//all the methods you defined in IDataStore<T>
}
create an abstract class which implements all the methods of IRepository
public abstract class RepositoryBase<T> : IRepository<T> where T : Entity
{
//all the methods of IRepository<T> are implemented in this class
//now you don't have problems when calling t.Id if t is of type T because compiler knows that T derives from Entity
}
create the specific interfaces for your entities
public interface ICarRepository : IRepository<Car>
{
//add car specific methods, i.e. IQueryable<Car> GetCarByCustomer(string customerId);
}
public interface ICustomerRepository : IRepository<Customer>
{
//add customer specific methods, i.e. Task<Customer> GetCustomerByCar(string carId);
}
Create the concrete classes
public class CarRepository : RepositoryBase<Car>, ICarRepository
{
//here you have to implement only car specific methods, i.e. IQueryable<Car> GetCarByCustomer(string customerId);
}
this is the most used approach, aka Repository Pattern