I am trying to extend the AuditLog entity in ASPNETBOILETPLATE framework in order to add some new properties to it. I have tried to extend the AuditLog class (ExtendedAuditInfo) and implement a customised version of AuditStore Class (ExtendedAuditStore). However, I am not able to inject my new ExtendedAuditInfo in the constructor and receive two error messages regarding unmatching input parameters in the Constructor and SaveAsync method.
Class ExtendedAuditInfo:
public class ExtendedAuditInfo : AuditInfo
{
// Some properties
}
Class ExtendedAuditStore:
public class ExtendedAuditStore : AuditingStore
{
public ExtendedAuditStore(IRepository<ExtendedAuditInfo, long> auditLogRepository)
: base(auditLogRepository)
{
}
public override Task SaveAsync(ExtendedAuditInfo auditInfo)
{
if (!string.IsNullOrEmpty(auditInfo.Parameters) && auditInfo.Parameters != "{}")
{
var parameters = JsonConvert.DeserializeObject<AuditParameterInput>(auditInfo.Parameters);
if (parameters != null)
auditInfo.CustomData = parameters.Input.Id.ToString();
}
return base.SaveAsync(auditInfo);
}
}
The errors are:
cannot convert from 'Abp.Domain.Repositories.IRepository<SixB.Serafina.Auditing.ExtendedAuditInfo, long>' to 'Abp.Domain.Repositories.IRepository<Abp.Auditing.AuditLog, long>'
and
no suitable method found to override
The procedure above is based on the idea that I found Here
I found the solution based on the official document of How To Extend Existing Entities.
In order to extend the AuditLog class, inheritance must be used. Therefore a new class, let's say ExtendedAuditInfo needs to be inherited from AuditLog.
public class ExtendedAuditLog : AuditLog
{
public ExtendedAuditLog()
{
}
public ExtendedAuditLog(AuditInfo auditInfo)
{
this.BrowserInfo = auditInfo.BrowserInfo;
this.ClientIpAddress = auditInfo.ClientIpAddress;
this.ClientName = auditInfo.ClientName;
this.CustomData = auditInfo.CustomData;
this.Exception = auditInfo.Exception?.Message.ToString() + "";
this.ExecutionDuration = auditInfo.ExecutionDuration;
this.ExecutionTime = auditInfo.ExecutionTime;
this.ImpersonatorTenantId = auditInfo.ImpersonatorTenantId;
this.ImpersonatorUserId = auditInfo.ImpersonatorUserId;
this.MethodName = auditInfo.MethodName;
this.Parameters = auditInfo.Parameters;
this.ReturnValue = auditInfo.ReturnValue;
this.ServiceName = auditInfo.ServiceName;
this.TenantId = auditInfo.TenantId;
this.UserId = auditInfo.UserId;
}
//new properties
}
This class has to be added to the context and obviously, a new migration needs to be run in order to add the new properties.
public class ProjectDbContext : AbpZeroDbContext<Tenant, Role, User, ProjectDbContext >
{
/* Define a DbSet for each entity of the application */
public SerafinaDbContext(DbContextOptions<SerafinaDbContext> options)
: base(options)
{
}
public virtual DbSet<County> Counties { get; set; }
public virtual DbSet<Country> Countries { get; set; }
public virtual DbSet<Currency> Currencies { get; set; }
public virtual DbSet<OrganisationType> OrganisationTypes { get; set; }
public virtual DbSet<ExtendedAuditLog> ExtendedAuditLogs { get; set; }
}
Finally, in the ExtendedAuditStore class, IRepository<ExtendedAuditLog, long> _extendedAuditLogRepository has to be injected as a second parameter of the constructor and can be used to insert the extended entity.
public class ExtendedAuditStore : AuditingStore
{
IRepository<ExtendedAuditLog, long> _extendedAuditLogRepository;
public ExtendedAuditStore(
IRepository<AuditLog, long> auditLogRepository,
IRepository<ExtendedAuditLog, long> extendedAuditLogRepository
)
: base(auditLogRepository)
{
_extendedAuditLogRepository = extendedAuditLogRepository;
}
public override async Task SaveAsync(AuditInfo auditInfo)
{
if (auditInfo.Exception != null)
await base.SaveAsync(auditInfo);
var auditLog = new ExtendedAuditLog(auditInfo);
//new properties can be set here
await _extendedAuditLogRepository.InsertAsync(auditLog);
}
}
Also, instead of inheriting from AuditingStore, a new implementation for IAuditingStore can be created and injected into application services.
UPDATE:
Finally, all you need is to replace the default AuditingStore in StartUp class:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddTransient<IAuditingStore, ExtendedAuditStore>();
}
Related
First question here, I hope I'm doing it right.
I'm using Entity Framework Core 5.0 (Code First) with an onion architecture (data/repo/service/mvc) and so I have a service for each table (almost).
It's work well but now I need to manage (get, insert, update, delete) about 150 tables which all have the same structure (Id, name, order).
I have added each of them as Entity class and their DbSet too in my DbContext, but I don't want to make 150 services, I would like to have a generic one .
How can I bind it to my generic repository ?
public class Repository<T> : IRepository<T> where T : BaseEntity
{
private readonly ApplicationContext context;
private DbSet<T> entities;
private readonly RepositorySequence repoSequence;
private string typeName { get; set; }
public Repository(ApplicationContext context)
{
this.context = context;
entities = context.Set<T>();
this.repoSequence = new RepositorySequence(context);
this.typeName = typeof(T).Name;
}
public T Get(long plng_Id)
{
return entities.SingleOrDefault(s => s.Id == plng_Id);
}
[...]
}
In an ideal world, would like to have something like this :
public async Task Insert(dynamic pdyn_Entity)
{
Type DynamicType = Type.GetType(pdyn_Entity);
Repository<DynamicType> vobj_Repo = new Repository<DynamicType>(mobj_AppContext);
long Id = await vobj_Repo.InsertAsync(pdyn_Entity);
}
But I can try to get type from DbSet string Name too, I just managed to retrieve some data :
public IEnumerable<object> GetAll(string pstr_DbSetName)
{
return ((IEnumerable<BaseEntity>)typeof(ApplicationContext).GetProperty(pstr_DbSetName).GetValue(mobj_AppContext, null));
}
I've tried the following method (2.0 compatible apparently) to get the good DbSet, not working neither (no Query) : https://stackoverflow.com/a/48042166/10359024
What am I missing?
Thanks a lot for your help
Not sure why you need to get type?
You can use something like this.
Repository.cs
public class Repository<T> : IRepository<T> where T : BaseEntity
{
private readonly ApplicationContext context;
private DbSet<T> entities;
public Repository(ApplicationContext context)
{
this.context = context;
entities = context.Set<T>();
}
public List<T> Get()
=> entities.ToList();
public T Get(long plng_Id)
=> entities.Find(plng_Id);
public long Save(T obj)
{
if (obj.ID > 0)
entities.Update(obj);
else
entities.Add(obj);
return obj.ID;
}
public void Delete(T obj)
=> entities.Remove(obj);
}
Then you can use either one of these 2 options you want
Multiple repositories following your tables
UserRepository.cs
public class UserRepository : Repository<User> : IUserRepository
{
private readonly ApplicationContext context;
public UserRepository(ApplicationContext context)
{
this.context = context;
}
}
BaseService.cs
public class BaseService : IBaseService
{
private readonly ApplicationContext context;
private IUserRepository user;
private IRoleRepository role;
public IUserRepository User { get => user ??= new UserRepository(context); }
public IRoleRepository Role { get => user ??= new RoleRepository(context); }
public BaseService(ApplicationContext context)
{
this.context = context;
}
}
If you are lazy to create multiple repositories, can use this way also. Your service just simple call Repository with entity name.
BaseService.cs
public class BaseService : IBaseService
{
private readonly ApplicationContext context;
private IRepository<User> user;
private IRepository<Role> role;
public IRepository<User> User { get => user ??= new Repository<User>(context); }
public IRepository<Role> Role { get => role ??= new Repository<Role>(context); }
public BaseService(ApplicationContext context)
{
this.context = context;
}
}
Finally, you can call service like this. You can use multiple services instead of BaseService if you want.
HomeController.cs
public class HomeController : Controller
{
private readonly IBaseService service;
public HomeController(IBaseService service)
{
this.service = service;
}
public IActionResult Index()
{
var user = service.User.Get();
return View(user);
}
public IActionResult Add(User user)
{
var id = service.User.Save(user);
return View();
}
}
I suggest to use first option (multiple repositories) because you may need to customise functions in own repository in future. And create service class following your controller name. For example, you have HomeController, UserController, etc. Create HomeService, UserService and link them with BaseService so that you can create customised functions in their own service class.
I assume you have a base entity like this:
public class BaseEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Order { get; set; }
}
Then you can do CRUD operations in your generic repository like this:
public int Create(T item)
{
if (item == null) return 0;
entities.Add(item);////SaveChanges
return item.Id;
}
public void Update(T updatedItem)
{
context.SetModified(updatedItem);//SaveChanges
}
public IQueryable<T> All()
{
return entities();
}
And in each of the methods you have access to your 3 common fields in BaseEntity
Thank you all for your responses.
I need to have the type because I am using a blazor component which automatically binds to these tables. This component has the name of the desired entity class (in string) as a parameter. Thanks to #Asherguru's response I was able to find a way to do this:
1 - I made a 'SedgmentEntity' Class :
public abstract class SegmentEntity : ISegmentEntity
{
public abstract long Id { get; set; }
public abstract string Name { get; set; }
public abstract short? Order { get; set; }
}
2 - A SegmentRepository which is typed via Reflection:
public class SegmentRepository : ISegmentRepository
{
private readonly ApplicationContext context;
private readonly RepositorySequence repoSequence;
public SegmentRepository(ApplicationContext context)
{
this.context = context;
this.repoSequence = new RepositorySequence(context);
}
public async Task<long> Insert(string pstr_EntityType, SegmentEntity pobj_Entity)
{
Type? vobj_EntityType = Assembly.GetAssembly(typeof(SegmentEntity)).GetType("namespace.Data." + pstr_EntityType);
if (vobj_EntityType != null)
{
// create an instance of that type
object vobj_Instance = Activator.CreateInstance(vobj_EntityType);
long? nextId = await repoSequence.GetNextId(GetTableName(vobj_EntityType));
if (nextId == null)
{
throw new TaskCanceledException("Sequence introuvable pour " + vobj_EntityType.FullName);
}
PropertyInfo vobj_PropId = vobj_EntityType.GetProperty("Id");
vobj_PropId.SetValue(vobj_Instance, nextId.Value, null);
PropertyInfo vobj_PropName = vobj_EntityType.GetProperty("Name");
vobj_PropName.SetValue(vobj_Instance, pobj_Entity.Name, null);
PropertyInfo vobj_PropOrder = vobj_EntityType.GetProperty("Order");
vobj_PropOrder.SetValue(vobj_Instance, pobj_Entity.Order, null);
return ((SegmentEntity)context.Add(vobj_Instance).Entity).Id;
}
}
public IEnumerable<object> GetAll(string pstr_EntityType)
{
Type? vobj_EntityType = Assembly.GetAssembly(typeof(SegmentEntity)).GetType("namespace.Data." + pstr_EntityType);
if (vobj_EntityType != null)
{
PropertyInfo vobj_DbSetProperty = typeof(ApplicationContext).GetProperties().FirstOrDefault(prop =>
prop.PropertyType.FullName.Contains(vobj_EntityType.FullName));
return (IEnumerable<object>)vobj_DbSetProperty.GetValue(context, null);
}
return null;
}
}
I still have to handle the Get and the Delete functions but it should be fine.
Then I will be able to create a single service which will be called by my component.
Thanks again !
How use Bounded Context Pattern in AspnetBoilerplate, Is there any way to use to use Bounded Context Pattern in AspnetBoilerplate?
Connect with multiple database in ASP.NET ZERO/ASP.NET BOILERPLATE.
Note - Use seperate DB Context to use multiple Databases.
Step 1. Create modal class in "MultipleDbContextEfCoreDemo.Core" Project for your tables.
[Table ("tblStudent")] //Is in First Database
public class Student : Entity<long> {
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
protected Student () { }
}
[Table ("tblCourses")] //Is in Second Database
public class Courses : Entity<long> {
public int ID { get; set; }
public string CourseName { get; set; }
public string Standard { get; set; }
protected Courses () { }
}
Step 2. In same project("MultipleDbContextEfCoreDemo.Core" Project) create/use "MultipleDbContextEfCoreDemoConsts.cs" file to add Database Connection names.
public class MultipleDbContextEfCoreDemoConsts
{
public const string LocalizationSourceName = "MultipleDbContextEfCoreDemo";
public const string ConnectionStringName = "Default";
public const string SecondDbConnectionStringName = "Second";
}
Step 3. In "MultipleDbContextEfCoreDemo.EntityFrameworkCore" Project goto "EntityFrameworkCore" Folder and create individual "DBContext" and "DbContextConfigurer" file for each database connection to which you want to connect.
FirstDatabase Setting -
required files to connect to first db -
1. FirstDbContext.cs
public class FirstDbContext : AbpDbContext, IAbpPersistedGrantDbContext {
/* Define an IDbSet for each entity of the application */
public DbSet<PersistedGrantEntity> PersistedGrants { get; set; }
public virtual DbSet<Student> Student { get; set; }
public FirstDbContext (DbContextOptions<FirstDbContext> options) : base (options) {
}
protected override void OnModelCreating (ModelBuilder modelBuilder) { }
}
2. FirstDbContextConfigurer
public static class FirstDbContextConfigurer {
public static void Configure (DbContextOptionsBuilder<FirstDbContext> builder, string connectionString) {
builder.UseSqlServer (connectionString);
}
public static void Configure (DbContextOptionsBuilder<FirstDbContext> builder, DbConnection connection) {
builder.UseSqlServer (connection);
}
}
SecondDatabase Setting -
required files to connect to second db -
1. SecondDbContext.cs
public class SecondDbContext : AbpDbContext, IAbpPersistedGrantDbContext {
/* Define an IDbSet for each entity of the application */
public DbSet<PersistedGrantEntity> PersistedGrants { get; set; }
public virtual DbSet<Student> Student { get; set; }
public SecondDbContext (DbContextOptions<SecondDbContext> options) : base (options) {
}
protected override void OnModelCreating (ModelBuilder modelBuilder) { }
}
2. SecondDbContextConfigurer
public static class SecondDbContextConfigurer {
public static void Configure (DbContextOptionsBuilder<SecondDbContext> builder, string connectionString) {
builder.UseSqlServer (connectionString);
}
public static void Configure (DbContextOptionsBuilder<SecondDbContext> builder, DbConnection connection) {
builder.UseSqlServer (connection);
}
}
Step 4. Then in same project("MultipleDbContextEfCoreDemo.EntityFrameworkCore") add "MyConnectionStringResolver.cs"
public class MyConnectionStringResolver : DefaultConnectionStringResolver
{
public MyConnectionStringResolver(IAbpStartupConfiguration configuration)
: base(configuration)
{
}
public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
{
if (args["DbContextConcreteType"] as Type == typeof(SecondDbContext))
{
var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder());
return configuration.GetConnectionString(MultipleDbContextEfCoreDemoConsts.SecondDbConnectionStringName);
}
return base.GetNameOrConnectionString(args);
}
}
Step 5. Then in same project("MultipleDbContextEfCoreDemo.EntityFrameworkCore"), Update "MultipleDbContextEfCoreDemoEntityFrameworkCoreModule.cs" file to replace the "IConnectionStringResolver" with our custom implementation MyConnectionStringResolver.
[DependsOn(typeof(MultipleDbContextEfCoreDemoCoreModule), typeof(AbpEntityFrameworkCoreModule))]
public class MultipleDbContextEfCoreDemoEntityFrameworkCoreModule : AbpModule
{
public override void PreInitialize()
{
Configuration.ReplaceService<IConnectionStringResolver, MyConnectionStringResolver>();
// Configure first DbContext
Configuration.Modules.AbpEfCore().AddDbContext<FirstDbContext>(options =>
{
if (options.ExistingConnection != null)
{
FirstDbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
}
else
{
FirstDbContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
}
});
// Configure second DbContext
Configuration.Modules.AbpEfCore().AddDbContext<SecondDbContext>(options =>
{
if (options.ExistingConnection != null)
{
SecondDbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
}
else
{
SecondDbContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
}
});
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(MultipleDbContextEfCoreDemoEntityFrameworkCoreModule).GetAssembly());
}
}
Step 6. Create the Service in "MultipleDbContextEfCoreDemo.Application" project with Dto, Interface and Service Class.
ITestAppService.cs-
public interface ITestAppService : IApplicationService
{
List<string> GetStudentAndCourses();
}
TestAppService.cs
public class TestAppService : MultipleDbContextEfCoreDemoAppServiceBase, ITestAppService
{
private readonly IRepository<Student> _studentRepository; //in the first db
private readonly IRepository<Courses> _coursesRepository; //in the second db
public TestAppService(
IRepository<Student> studentRepository,
IRepository<Courses> coursesRepository
)
{
_studentRepository = studentRepository;
_coursesRepository = coursesRepository;
}
//a sample method uses both databases concurrently
public List<string> GetStudentAndCourses()
{
List<string> names = new List<string>();
var studentNames = _studentRepository.GetAllList().Select(p => "Student: " + p.FirstName).ToList();
names.AddRange(peopleNames);
var courseNames = _coursesRepository.GetAllList().Select(p => "Course: " + p.CourseName).ToList();
names.AddRange(courseNames);
return names;
}
}
Step 7. Add Database connectionStrings to your MultipleDbContextEfCoreDemo.Web/MultipleDbContextEfCoreDemo.Web.Host project's
"appsettings.json" file.
{
"ConnectionStrings": {
"Default":
"Server=XXX.XXX.XX.XX;Database=firstDB;Integrated Security=False;TrustServerCertificate=True;User ID=XX;Password=XXX;",
"Second":
"Server=XXX.XXX.XX.XX;Database=secondDB;Integrated Security=False;TrustServerCertificate=True;User ID=XX;Password=XXX;"
}
}
Step 8. Use Service in your angular/MVC project.
With EF you need one complete dbcontext for migrations.
Create other "bounded" dbcontext, with the entities to be ignored in modelbuilder, then use this in appservice.
Very simple answer
HTH
I am trying to figure out how to create a second DB context in EF7 RC1. In the past I could use a constructor with :base("connectionName") but that no longer seems an option since it says cannot convert string to System.IServiceProvider.
My second context code is as follows:
public class DecAppContext : DbContext
{
public DecAppContext()
// :base("DefaultConnection")
{
}
public DbSet<VignetteModels> VignetteModels { get; set; }
public DbSet<VignetteResult> Result { get; set; }
}
}
In my config.json I have the connection specified:
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-xxxxx...;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
In my configure services section of my startup I have both contexts added:
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]))
.AddDbContext<DecAppContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
The applicationDB context works fine since I can create a user and login without issue
However when I try to access the other context as in my controller via:
private DecAppContext db = new DecAppContext();
var vignette = db.VignetteModels.SingleOrDefault(v => v.CaseId == vid);
I get the error:
No database providers are configured. Configure a database provider by
overriding OnConfiguring in your DbContext class or in the
AddDbContext method when setting up services.
Any working examples in EF7 RC1 with multiple db contexts and accessing them would be much appreciated.
First of all I would recommend you the article from the wiki of EntityFramework on GitHub. The article describes many ways to define DbContext, which references to a section of appsettings.json. I personally prefer the way with the usage of [FromServices] attribute.
The code could be about the following:
First of all you defined appsettings.json with the following content
{
"Data": {
"ApplicationDbConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ApplicationDb;Trusted_Connection=True;MultipleActiveResultSets=true",
"DecAppDbConnectionString": "Server=Server=(localdb)\\mssqllocaldb;Database=DecAppDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
where you define two connection strings.
Seconds you declare the classes DecAppContext and ApplicationDbContext which have DbContext as the base class. The simplest form will be just
public class ApplicationDbContext : DbContext
{
}
public class DecAppContext : DbContext
{
}
without any DbSet properties.
Third Step. You use Microsoft.Extensions.DependencyInjection to inject the database contexts. To do this you need just include in Startup.cs something like
public class Startup
{
// property for holding configuration
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
// save the configuration in Configuration property
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options => {
options.UseSqlServer(Configuration["Data:ApplicationDbConnectionString"]);
})
.AddDbContext<DecAppContext>(options => {
options.UseSqlServer(Configuration["Data:DecAppDbConnectionString"]);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
}
}
Se create two DbContext (DecAppContext and ApplicationDbContext) using the configuration "Data:DecAppDbConnectionString" and "Data:ApplicationDbConnectionString".
Now we can just use the context in the controller. For example
[Route("api/[controller]")]
public class UsersController : Controller
{
[FromServices]
public ApplicationDbContext ApplicationDbContext { get; set; }
[FromServices]
public DecAppContext DecAppContext { get; set; }
[HttpGet]
public IEnumerable<object> Get() {
var returnObject = new List<dynamic>();
using (var cmd = ApplicationDbContext.Database.GetDbConnection().CreateCommand()) {
cmd.CommandText = "SELECT Id, FirstName FROM dbo.Users";
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
var retObject = new List<dynamic>();
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var iFiled = 0; iFiled < dataReader.FieldCount; iFiled++)
dataRow.Add(
dataReader.GetName(iFiled),
dataReader.IsDBNull(iFiled) ? null : dataReader[iFiled] // use null instead of {}
);
retObject.Add((ExpandoObject)dataRow);
}
}
return retObject;
}
}
}
or the same using async/await:
[Route("api/[controller]")]
public class UsersController : Controller
{
[FromServices]
public ApplicationDbContext ApplicationDbContext { get; set; }
[FromServices]
public DecAppContext DecAppContext { get; set; }
[HttpGet]
public async IEnumerable<object> Get() {
var returnObject = new List<dynamic>();
using (var cmd = ApplicationDbContext.Database.GetDbConnection().CreateCommand()) {
cmd.CommandText = "SELECT Id, FirstName FROM dbo.Users";
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
var retObject = new List<dynamic>();
using (var dataReader = await cmd.ExecuteReaderAsync())
{
while (await dataReader.ReadAsync())
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var iFiled = 0; iFiled < dataReader.FieldCount; iFiled++)
dataRow.Add(dataReader.GetName(iFiled), dataReader[iFiled]);
retObject.Add((ExpandoObject)dataRow);
}
}
return retObject;
}
}
}
One can just declare the property public ApplicationDbContext ApplicationDbContext { get; set; } with the attribute [FromServices] and ASP.NET initialize it from the context injected in ConfigureServices. In the same way one can use the second context DecAppContext whenever you need it.
The above code example will execute SELECT Id, FirstName From dbo.Users in the database context and return JSON data in the form [{"id":123, "firstName":"Oleg"},{"id":456, "firstName":"Xaxum"}]. The conversion of property names from Id and FirstName to id and firstName will be done automatically during serialization because of usage AddJsonOptions in ConfigureServices.
UPDATE: I have to reference the announcement. The next version of MVC (RC2) will require to change the above code to use [FromServices] as additional parameter (of method Get() for example) instead of usage public property [FromServices] public ApplicationDbContext ApplicationDbContext { get; set; }. One will need to remove the property ApplicationDbContext and to add additional parameter to Get() method: public async IEnumerable<object> Get([FromServices] ApplicationDbContext applicationDbContext) {...}. Such changes can be easy done. See here and example of the changes in the demo example of MVC:
[Route("api/[controller]")]
public class UsersController : Controller
{
[HttpGet]
public async IEnumerable<object> Get(
[FromServices] ApplicationDbContext applicationDbContext,
[FromServices] DecAppContext decAppContext)
{
var returnObject = new List<dynamic>();
// ... the same code as before, but using applicationDbContext
// and decAppContext parameters instead of ApplicationDbContext
// and DecAppContext properties
}
Please can someone help me because I am getting confused.
I have an Entity like this:
public class Code
{
public int ID { get; set; }
public int UserID { get; set; }
public string CodeText { get; set; }
}
and an Interface like this:
public interface ICodeRepository
{
IQueryable<Code> Codes { get; }
void AddCode(Code code);
void RemoveCode(Code code);
Code GetCodeById(int id);
}
and a Repository like this:
public class SQLCodeRepository : ICodeRepository
{
private EFSQLContext context;
public SQLCodeRepository()
{
context = new EFSQLContext();
}
public IQueryable<Code> Codes
{
get { return context.Codes; }
}
public void AddCode(Code code)
{
context.Codes.Add(code);
context.SaveChanges();
}
public void RemoveCode(Code code)
{
context.Codes.Remove(code);
context.SaveChanges();
}
public Code GetCodeById(int id)
{
return context.Codes.Where(x => x.ID == id).FirstOrDefault();
}
}
and a Context like this:
public class EFSQLContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Code> Codes { get; set; }
public DbSet<PortfolioUser> PortfolioUsers { get; set; }
}
If I declare my controller like this:
public class SearchController : Controller
{
private ICodeRepository cRepo;
public SearchController(ICodeRepository codeRepository)
{
cRepo = codeRepository;
}
}
and then try to do cRepo.GetCodeById(1) nothing happens. But if I declare private ICodeRepository rep = new SQLCodeRepository and then call rep.GetCodeById(1) I can see the method in the Repository being called.
What am I doing wrong?
It looks like from the constructor signature, you are going to be doing some dependency injection. The step you are missing is to set up a DI container using a tool like Castle Windsor. You then configure the MVC resolver to use the DI container to give you the correct implementation of ICodeRepository.
See this
You'll need to create a resolver that implements IDependencyResolver and IDependencyScope and a controller factory that inheritsDefaultControllerFactory
Once you have those you can do something like the following:
MyContainer container; // this needs to be a class level member of the asax
var configuration = GlobalConfiguration.Configuration;
container = new MyContainer() // may need additional stuff here depending on DI tool used
configuration.DependencyResolver = new MyDependancyResolver(container);
var mvcControllerFactory = new MyFactory(container.Kernel);
ControllerBuilder.Current.SetControllerFactory(mvcControllerFactory);
You would call the above code from the asax Application_Start()
See this answer for more specifics on using Ninject and MVC3
I am fairly new to MVC, but after playing with it (MVC 3/Razor), I am hooked.
I have a few questions:
1) What is the best, or most widely used pattern to develop MVC apps in? Repository, DDD, UOW?
2) I am using the Entity Framework 4, so could some please explain to me or point me to a good source that will explain the Repository Pattern w/EF4? Doesn't EF4 take place as the business layer and the data access layer? Does the Repository Pattern even provide a benefit?
3) Also, one last question, could someone explain the whole relationship between the Controller, the Model and the View? I get the basics, but maybe a little more in depth of the correct way to use it. View Models - Say I have a view that displays customer info, and one that edits it, should I have a view model and an edit model, or can the be passed around?
4) Examples??
Thanks for the help up front,
$("Sam")
** EDIT **
Am I on the right track here:
Public Class HomeController
Inherits System.Web.Mvc.Controller
Function Index(ByVal id As Integer) As ActionResult
Return View(New HomeModel)
End Function
<HttpPost()> _
Function Index(ByVal Model As HomeModel) As ActionResult
Return View(Model)
End Function
End Class
Public Class HomeModel
Private _Repository As IRepository(Of Customer)
Public Property Customer As Customer
Public Sub New()
End Sub
Public Sub New(ByVal ID As Integer)
_Repository = New CustomerRepository
Customer = _Repository.GetByID(ID)
End Sub
End Class
Public Interface IRepository(Of T)
Function GetByID(ByVal ID As Integer) As T
Sub Add(ByVal Entity As T)
Sub Delete(ByVal Entity As T)
End Interface
Public Class CustomerRepository
Implements IRepository(Of Customer)
Public Sub Add(ByVal Entity As Customer) Implements IRepository(Of Customer).Add
End Sub
Public Sub Delete(ByVal Entity As Customer) Implements IRepository(Of Customer).Delete
End Sub
Public Function GetByID(ByVal ID As Integer) As Customer Implements IRepository(Of Customer).GetByID
Return New Customer With {.ID = ID, .FirstName = "Sam", .LastName = "Striano"}
End Function
End Class
Public Class Customer
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
End Class
I use generic repositories that get instantiated in a service class (using Dependency Injection with Ninject).
The service class essentially performs two functions:
It provides all the methods that the controller will consume.
It has a property called ViewModel, that essentially maps the data that the views need into a MyViewModel class.
The Controller consumes the service class. With this "pattern", your controllers look like:
namespace ES.eLearningFE.Areas.Courses.Controllers
{
public partial class CourseController : Controller
{
ICourseDisplayService service;
public CourseController(ICourseDisplayService service)
{
this.service = service;
}
public virtual ActionResult Display(int CourseId, int StepOrder, string PupilName, string TutorName)
{
service.CourseId = CourseId;
service.StepOrder = StepOrder;
service.PupilName = PupilName;
service.TutorName = TutorName;
if (Request.IsAjaxRequest())
{
return PartialView(service.ViewModel);
}
else
{
return View(service.ViewModel);
}
}
}
}
The ViewModel class only hold display data and no methods (except the odd really simple method to retrieve data from another property that is, for example a List<> object).
Works really well. An example of a service class:
namespace ES.eLearning.Domain.Services.Courses
{
public class SqlCourseDisplayService : ICourseDisplayService
{
DataContext db;
public SqlCourseDisplayService(DbDataContextFactory contextFactory)
{
db = contextFactory.Make();
CoursesRepository = new SqlRepository<Course>(db);
StepsRepository = new SqlRepository<CourseStep>(db);
StepLinksRepository = new SqlRepository<StepLink>(db);
UserCoursesRepository = new SqlRepository<UserCourse>(db);
CourseTutorsRepository = new SqlRepository<CourseTutor>(db);
UsersRepository = new SqlRepository<User>(db);
}
#region ICourseDisplayService Members
public ViewModels.CourseDisplayVM ViewModel
{
get
{
return new ViewModels.CourseDisplayVM
{
CourseId = this.CourseId,
CourseName = this.Course.Name,
Steps = this.Steps,
ActiveStepIndex = this.ActiveStepIndex,
CurrentStepIndex = this.CurrentStepIndex,
Pupil = new UserDto { UserId = this.PupilId, UserName = this.PupilName },
Tutors = this.GetTutors(this.CourseId),
Tutor = tutorName == null ? null : new UserDto { UserName = this.TutorName, UserId = this.TutorId}
};
}
}
#region Entities
int courseId;
public int CourseId
{
get
{
if (courseId == 0) throw new ApplicationException("Invalid Course Id!");
return courseId;
}
set
{
if (value == 0) throw new ApplicationException("Invalid Course Id!");
try
{
Course = (from c in CoursesRepository.Query where c.CourseId == value select c).First();
Steps = Course.CourseSteps.ToList();
courseId = value;
}
catch {throw new ApplicationException("No Course found for Course Id: " + value);}
}
}
public Data.Course Course { get; private set; }
public int StepOrder { get; set; }
public List<Data.CourseStep> Steps { get; private set; }
public int ActiveStepIndex
{
get
{
if (PupilName == null)
{
throw new ApplicationException("Pupil not set!");
}
if (CourseId == 0)
{
throw new ApplicationException("Course not set!");
}
try
{
var x = (from uc in UserCoursesRepository.Query where (uc.IdCourse == CourseId) && (uc.UserName == PupilName) select uc).First();
return x.ActiveStepIndex;
}
catch { throw new ApplicationException("Could not get Active Step!"); }
}
}
#endregion
#region Users
string tutorName;
public string TutorName
{
get
{
if (tutorName == null) throw new ApplicationException("Invalid call to get Tutor Name [Null Tutor Name]!");
return tutorName;
}
set
{
tutorName = value;
TutorId = (Guid)Membership.GetUser(tutorName).ProviderUserKey;
}
}
public Guid TutorId { get; set; }
string pupilName;
public string PupilName
{
get { return pupilName; }
set
{
pupilName = value;
PupilId = (Guid)Membership.GetUser(pupilName).ProviderUserKey;
}
}
public Guid PupilId { get; set; }
#endregion
#region Utility Properties
public int CurrentStepIndex { get; set; }
public int StepCount
{
get
{
return Steps == null ? 0 : Steps.Count();
}
}
#endregion
#region Private Utilities
private List<UserDto> GetTutors(int CourseId)
{
return (from ct in CourseTutorsRepository.Query join u in UsersRepository.Query
on ct.TutorName equals u.UserName
where (ct.CourseId == courseId)
select new UserDto { UserName = ct.TutorName, UserId = u.UserId }).ToList();
}
#endregion
#region Repositories
private IRepository<Course> CoursesRepository
{
get;
set;
}
private IRepository<CourseStep> StepsRepository
{
get;
set;
}
private IRepository<StepLink> StepLinksRepository
{
get;
set;
}
private IRepository<UserCourse> UserCoursesRepository
{
get;
set;
}
private IRepository<CourseTutor> CourseTutorsRepository
{
get;
set;
}
private IRepository<User> UsersRepository
{
get;
set;
}
#endregion
#endregion
}
}
May not be everyone's choice, but hey, it works for me... AND (more importantly) my clients and their users.
Edit
As requested in the comment below, the Repository that I use:
namespace ES.eLearning.Domain
{
public class SqlRepository<T> : IRepository<T> where T : class
{
DataContext db;
public SqlRepository(DataContext db)
{
this.db = db;
}
#region IRepository<T> Members
public IQueryable<T> Query
{
get { return db.GetTable<T>(); }
}
public List<T> FetchAll()
{
return Query.ToList();
}
public void Add(T entity)
{
db.GetTable<T>().InsertOnSubmit(entity);
}
public void Delete(T entity)
{
db.GetTable<T>().DeleteOnSubmit(entity);
}
public void Attach(T entity)
{
db.GetTable<T>().Attach(entity);
}
public void Save()
{
db.SubmitChanges();
}
#endregion
}
}
And the IRepository Interface:
namespace Wingspan.Web.Mvc
{
public interface IRepository<TEntity> where TEntity : class
{
List<TEntity> FetchAll();
IQueryable<TEntity> Query {get;}
void Add(TEntity entity);
void Delete(TEntity entity);
void Attach(TEntity entity);
void Save();
}
}
This should help you getting started. There are a lot of tutorials and videos available; for example:
Understanding Models, Views and Controllers
The ASP.NET MVC 2.0 basics and excellent introduction by Scott Hanselman. Personally one of my favorite speakers.
And also at www.asp.net; there are a few tutorials/examples to help you getting started. For example the Music Store sample
Unfortunately, I'm not so familiar with EF4/Repository pattern. But here's a blogpost about this pattern.
1) I would say that the repository pattern is the most widely used, then there is inversion of controll too.
2) I can't really point out the benefits with using a repository for entity framework other than that the controller should not know about how to acces data other then asking a repository. This makes it easy to switch it out sometime.
You can also eager load the data to make sure that the view don't call the database in every iteration of a foreach, for example a collection of users to display data from a child entity. You can probly do this anyway, but I feel that the repository is the right place to do it.
3) I can't tell you about the concept in a more in depth way, but I can tell some about viewmodels. In my opinion you should only use viewmodels if there is anything more then one entity you want to send to the view, for example a list of countries. You can alo use a viewmodel to "flatten" out very complex objects.
I would defiantly say the repository pattern is used a lot. This pattern can be used with Dependency Injection. Using Dependency Injection makes Unit Testing a breeze because you can snap different repositories to an abstract repoistory. Check out http://ninject.org/ for a simple to use Dependecy injector for .NET.
View Models should hold display data and transfer that data from the controller to the view. If you want to edit and display customer info, take a look at this