A database plugin can perform database operations against EvenCart database to extend default EvenCart software.
Before we create a database plugin, we need to create database versions for our plugin.
The following document describes procedure to create a Database plugin.
Creating the plugin class
A plugin class that inherits EvenCart.Services.Plugins.DatabasePlugin
class becomes a database plugin class.
Within the plugin class, we'll need to tell EvenCart about our database versions. To do this, we'll need to override the method GetDatabaseVersions
and return the list of database versions that our plugin has.
A database plugin class at the very minimum look like below.
using System.Collections.Generic;
using DotEntity.Versioning;
using EvenCart.Services.Plugins;
using EvenCart.Infrastructure;
using My.Plugin.Versions;
namespace My.Plugin
{
public class MyDbPlugin : DatabasePlugin
{
public override IList<IDatabaseVersion> GetDatabaseVersions()
{
var versions = base.GetDatabaseVersions();
versions.Add(new Version_1_0());
return versions;
}
}
}
In the above example, a thing worth nothing is that the GetDatabaseVersion
method first calls it's base class method. This is essential for a database plugin to work correctly.
Creating data access services
Creating data access services in EvenCart is very easy and follows generic inheritance and implementation to reduce the amount of code required to perform database operations.
EvenCart exposes a generic interface called EvenCart.Core.Services.IFoundationEntityService<T>
and a generic abstract class EvenCart.Core.Services.FoundationEntityService<T>
that implements the interface. The class FoundationEntityService<T>
contains full implementation for all the methods. Thus any sub classes automatically implement the same methods.
In order to write data access services, two things are required.
- An interface that inherits the interface
IFoundationEntityService<T>
with the actual domain entity class as it's generic type parameter. We can add more method signatures to this interface as per our requirements. - A class that inherits the class
FoundationEntityService<T>
with the actual domain entity class as it's generic type parameter and implements the interface created above in step 1.
For example if we need to create data access services for our entity called Book
from our domain entities example, we'll have the following interface and classes.
Data Access Interface
using EvenCart.Core.Services;
using My.Plugin.Data;
namespace My.Plugin.Services
{
public interface IBookService : IFoundationEntityService<Book>
{
}
}
Data Access Implementation
using EvenCart.Core.Services;
using My.Plugin.Data;
namespace My.Plugin.Services
{
public class BookService : FoundationEntityService<Book>, IBookService
{
}
}
Note how we pass our domain entity Book
as generic type parameter to both the constructs above.
Now we just need to register our service with the dependency container.
...
registrar.Register<IBookService, BookService>();
...
You can read more about dependency injection here. Our service interface IBookService
will now be available for resolution within Controllers or anywhere else.
Data access methods
FoundationEntityService<T>
implements the following methods.
Method |
---|
void Insert(T entity, Transaction transaction = null) Inserts the entity into the database. If transaction is provided, the query executes within a transaction. |
void Insert(T[] entity) Inserts multiple entities into the database with a single database connection. |
void InsertOrUpdate(T entity, Transaction transaction = null) Inserts or update the provided entity. If the Id column is greater then 0, then an update is performed. Otherwise an insert is performed. If transaction is provided, the query executes within a transaction. |
void Update(T entity, Transaction transaction = null) Updates the database row corresponding to the entity in the database. If transaction is provided, the query executes within a transaction. |
void Update(object entity, Expression<Func<T, bool>> where, Transaction transaction, Func<T, bool> action = null) Updates the columns provided by the object entity based on the where condition. The entity object should be a dynamic object with key-value pairs. If transaction is provided, the query executes within a transaction. |
void Delete(T entity, Transaction transaction = null) Deletes the matching entity from database. If transaction is provided, the query executes within a transaction. |
void Delete(Expression<Func<T, bool>> where, Transaction transaction = null) Deletes the entities matching with where expression. If transaction is provided, the query executes within a transaction. |
T Get(int id) Gets the entity with matching Id column. |
IEnumerable<T> Get(Expression<Func<T, bool>> where, int page = 1, int count = int.MaxValue) Gets all the entities matching with where expression. |
IEnumerable<T> Get(out int totalResults, Expression<Func<T, bool>> where, Expression<Func<T, object>> orderBy = null, RowOrder rowOrder = RowOrder.Ascending, int page = 1, int count = int.MaxValue) Gets all the entities matching with where expression ordered by the orderBy expression. If no orderBy expression is provided, the entities are ordered by primary key.The out parameter totalResults returns total number rows that match the provided expression. |
T FirstOrDefault(Expression<Func<T, bool>> where)
Returns the first entity that matches the |
IEnumerable<T> Query(string query, object parameters = null) Returns all the entities from the result of provided query. Use dynamic object to pass the parameter to the query. |
int Count(Expression<Func<T, bool>> where = null) Returns the count of entities that matches the where expression. |
As you can see, the methods are similar to those exposed by DotEntity
.
Consuming the services
In order to consume the service we can inject the service interface into our controller (or manually resolve the interface as described here if you are working with the service outside the controller).
In the following example, we setup a controller, inject the IBookService
and consume the service inside our action method.
public class BooksController : FoundationController
{
private readonly IBookService _bookService;
public BooksController(IBookService bookService)
{
_bookService = bookService;
}
public IActionResult Get(int id)
{
var book = _bookService.Get(id);
return R.Success.With("book", book).Result;
}
}
The above example fetches the Book
with the provided id from the table MARKDOWN_HASHd553e76263385cbb18947c5069239b3bMARKDOWNHASH
*(All the table names in EvenCart start with the prefix ec)*
Note however that the virtual property Pages
inside book object will be null because the default service doesn't fetch the related entities automatically.
The next section summarizes how we can override default functionality to fetch the data from Page
table.
Customizing the services
There are times when default implementations from FoundationService<T>
are limiting your queries. Because all the methods are declared as virtual
you can always override any of the above methods.
For example, in the BookService
above, the default implementation of Get(id)
will fetch the Book
matching with the provided Id
. It however doesn't populate the related entity Page.
To achieve that, we'll override the Get(id)
method in our BookService
to include a join.
using EvenCart.Core.Services;
using My.Plugin.Data;
namespace My.Plugin.Services
{
public class BookService : FoundationEntityService<Book>, IBookService
{
public override Book Get(int id)
{
return Repository.Join<Page>("Id", "BookId", joinType: JoinType.LeftOuter)
.Relate(RelationTypes.OneToMany<Book, Page>())
.Where(x => x.Id == id)
.SelectNested()
.FirstOrDefault();
}
}
}
In the above example, we have provided a new implementation for Get(id)
method that fetches linked Page
rows. The relate function is used to define the relationship one-to-many from Book to Page.
Note that each Page
object also automatically gets it's Book
virtual property assigned.
The Repository
in the above code is a wrapper around EntitySet<T>
from DotEntity library.