Currently we use the UnitOfWork design pattern which we cannot change.
The generic repository class looks like this:
public class Repository<T> : IDisposable, IRepository<T> where T : class
{
// Create our private properties
private readonly DbContext context;
private readonly DbSet<T> dbEntitySet;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="context">The database context</param>
public Repository(DbContext context)
{
// If no context is supplied, throw an error
if (context == null)
throw new ArgumentNullException("context");
// Assign our context and entity set
this.context = context;
this.dbEntitySet = context.Set<T>();
}
/// <summary>
/// Allows the execution of stored procedures
/// </summary>
/// <typeparam name="T">The entity model</typeparam>
/// <param name="sql">The name of the stored procedure</param>
/// <param name="parameters">Option list of parameters</param>
/// <returns></returns>
public DbRawSqlQuery<T> SQLQuery(string sql, params object[] parameters)
{
return this.context.Database.SqlQuery<T>(sql, parameters);
}
/// <summary>
/// Gets all the entities
/// </summary>
/// <param name="includes">Option includes for eager loading</param>
/// <returns></returns>
public IQueryable<T> GetAll(params string[] includes)
{
IQueryable<T> query = this.dbEntitySet;
foreach (var include in includes)
query = query.Include(include);
return query;
}
/// <summary>
/// Creates an entity
/// </summary>
/// <param name="model"></param>
public void Create(T model)
{
this.dbEntitySet.Add(model);
}
/// <summary>
/// Updates an entity
/// </summary>
/// <param name="model"></param>
public void Update(T model)
{
this.context.Entry<T>(model).State = EntityState.Modified;
}
/// <summary>
/// Removes an entity
/// </summary>
/// <param name="model"></param>
public void Remove(T model)
{
this.context.Entry<T>(model).State = EntityState.Deleted;
}
/// <summary>
/// Dispose method
/// </summary>
public void Dispose()
{
this.context.Dispose();
}
and the service looks like this:
public class Service<T> where T : class
{
private readonly IUnitOfWork unitOfWork;
private readonly IRepository<T> repository;
protected IUnitOfWork UnitOfWork
{
get { return this.unitOfWork; }
}
protected IRepository<T> Repository
{
get { return this.repository; }
}
public Service(IUnitOfWork unitOfWork)
{
if (unitOfWork == null)
throw new ArgumentNullException("unitOfWork");
this.unitOfWork = unitOfWork;
this.repository = unitOfWork.GetRepository<T>();
}
}
and the UnitOfWork class looks like this:
/// <summary>
/// Used to handle the saving of database changes
/// </summary>
/// <typeparam name="TContext"></typeparam>
public class UnitOfWork<TContext> : IUnitOfWork where TContext : DbContext, new()
{
// Private properties
private readonly DbContext context;
private Dictionary<Type, object> repositories;
// Public properties
public DbContext Context { get { return this.context; } }
/// <summary>
/// Default constructor
/// </summary>
public UnitOfWork()
{
this.context = new TContext();
repositories = new Dictionary<Type, object>();
}
/// <summary>
/// Gets the entity repository
/// </summary>
/// <typeparam name="TEntity">The entity model</typeparam>
/// <returns></returns>
public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class
{
// If our repositories have a matching repository, return it
if (repositories.Keys.Contains(typeof(TEntity)))
return repositories[typeof(TEntity)] as IRepository<TEntity>;
// Create a new repository for our entity
var repository = new Repository<TEntity>(context);
// Add to our list of repositories
repositories.Add(typeof(TEntity), repository);
// Return our repository
return repository;
}
/// <summary>
/// Saves the database changes asynchronously
/// </summary>
/// <returns></returns>
public async Task SaveChangesAsync()
{
try
{
// Save the changes to the database
await this.context.SaveChangesAsync();
// If there is an error
} catch (DbEntityValidationException ex) {
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
this.context.Dispose();
}
}
Now I have a ProductService which looks like this:
/// <summary>
/// Handles all product related methods
/// </summary>
public class ProductService : Service<Product>
{
/// <summary>
/// The default constructor
/// </summary>
/// <param name="unitOfWork"></param>
public ProductService(IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
/// <summary>
/// Gets all products
/// </summary>
/// <param name="includes">Optional eager loading includes</param>
/// <returns></returns>
public async Task<List<Product>> GetAllAsync(params string[] includes)
{
// Return as an asynchronous list
return await this.Repository.GetAll(includes).ToListAsync();
}
/// <summary>
/// Gets a single product by id
/// </summary>
/// <param name="id">The id of the product</param>
/// <param name="includes">Optional eager loading includes</param>
/// <returns></returns>
public async Task<Product> GetAsync(int id, params string[] includes)
{
var models = await this.GetAllAsync(includes);
return models.Where(model => model.Id == id).SingleOrDefault();
}
/// <summary>
/// Create a product
/// </summary>
/// <param name="model">The product model</param>
public void Create(Product model)
{
// Create a team
this.Repository.Create(model);
}
/// <summary>
/// Update a product
/// </summary>
/// <param name="model">The product model</param>
public void Update(Product model)
{
// Update a team
this.Repository.Update(model);
}
/// <summary>
/// Delete a product
/// </summary>
/// <param name="model">The product model</param>
public void Remove(Product model)
{
// Remove a team
this.Repository.Remove(model);
}
}
I have set up my Code First database to use Cascading Delete when deleting a product. There are quite a few 1 to 1 table relationships set up. If I use SQL Management Studio I can manually delete a product from the database and it will delete all the related entries.
But, if I try to delete it from my application I get this error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
Ideally I would like to loop through the children of a generic class (so in the Reposity class) and check to see if cascading delete is enabled for that entity, if it is I would like to delete it.
If not, do nothing.
Does anyone know if that is possible or know of another solution that does not involve dropping the UnitOfWork design pattern?
Related
Please I have a problem with Entity framework core in ASP.Net core.
First of Iam an amateur in both EF Core and ASP.Net Core.
Idea:
Iam trying to create a generic repository to do some repeated routins without repeat the code with when I work with multiple contexts.
The Repo class:
public sealed class Repo<TContext> : IRepo<TContext>, IDisposable where TContext : DbContext, new()
{
#region properties
/// <summary>
/// Private DBContext property
/// </summary>
private DbContext _Context { get; } = null;
/// <summary>
/// Determine if Lazy Loading either activate or not
/// </summary>
private bool _LazyLoaded { get; set; }
#endregion
#region Construcors
public Repo(bool LazyLoaded)
{
_Context = new TContext();
_LazyLoaded = LazyLoaded;
_Context.ChangeTracker.LazyLoadingEnabled = LazyLoaded;
}
#endregion
#region Routines
#region Select
/// <summary>
/// Get All records from a table
/// </summary>
/// <typeparam name="TEntity">The entity to select from</typeparam>
/// <returns></returns>
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
{
return _Context.Set<TEntity>().ToList();
}
/// <summary>
/// Asynchronously, Get All records from a table
/// </summary>
/// <typeparam name="TEntity">The entity to select from</typeparam>
/// <returns></returns>
public Task<IEnumerable<TEntity>> GetAllAsync<TEntity>() where TEntity : class
{
return Task.Factory.StartNew(() => GetAll<TEntity>());
}
/// <summary>
/// Get One record from a table, based on the primary key value
/// </summary>
/// <typeparam name="TEntity">The entity to select from</typeparam>
/// <param name="pkValue">The primary key value</param>
/// <returns></returns>
public TEntity GetOne<TEntity>(object pkValue) where TEntity : class
{
return _Context.Set<TEntity>().Find(pkValue);
}
/// <summary>
/// Asynchronously, Get One record from a table, based on the primary key value
/// </summary>
/// <typeparam name="TEntity">The entity to select from</typeparam>
/// <param name="pkValue">The primary key value</param>
/// <returns></returns>
public Task<TEntity> GetOneAsync<TEntity>(object pkValue) where TEntity : class
{
return Task.Factory.StartNew(() => GetOne<TEntity>(pkValue));
}
#region Preview feature
/// <summary>
/// Get Many records from a table based on a property value
/// </summary>
/// <typeparam name="TEntity">The entity to select from</typeparam>
/// <param name="prop">The property used in the condition</param>
/// <param name="val">The value that will used in the search</param>
/// <returns></returns>
public IEnumerable<TEntity> GetMany<TEntity>(string prop, object val) where TEntity : class
{
return _Context.Set<TEntity>().AsEnumerable()
.Where(x => typeof(TEntity).GetProperty(prop).GetValue(x, null).ToString()
.Contains(val.ToString())).ToList();
}
#endregion
#endregion
#region Contains
/// <summary>
/// Check if a entity contains an object
/// </summary>
/// <typeparam name="TEntity">Entity to be look in</typeparam>
/// <param name="obj">The object to be looking for</param>
/// <returns></returns>
public bool Contains<TEntity>(TEntity obj) where TEntity : class
{
return _Context.Set<TEntity>().AsEnumerable().Contains(obj);
}
/// <summary>
/// Asynchronously Check if a entity contains an object
/// </summary>
/// <typeparam name="TEntity">Entity to be look in</typeparam>
/// <param name="obj">The object to be looking for</param>
/// <returns></returns>
public Task<bool> ContainsAsync<TEntity>(TEntity obj) where TEntity : class
{
return Task.Factory.StartNew(() => Contains<TEntity>(obj));
}
/// <summary>
/// Check if a entity contains an object based on a custom EQUALITY Comparer
/// </summary>
/// <typeparam name="TEntity">Entity to be look in</typeparam>
/// <typeparam name="TEntityComparer">The custom TEntity EQUALITY Comparer</typeparam>
/// <param name="obj">The object to be looking for</param>
/// <returns></returns>
public bool Contains<TEntity, TEntityComparer>(TEntity obj)
where TEntity : class
where TEntityComparer : IEqualityComparer<TEntity>, new()
{
return _Context.Set<TEntity>().AsEnumerable().Contains(obj,new TEntityComparer() as IEqualityComparer<TEntity>);
}
/// <summary>
/// Asynchronously Check if a entity contains an object based on a custom EQUALITY Comparer
/// </summary>
/// <typeparam name="TEntity">Entity to be look in</typeparam>
/// <typeparam name="TEntityComparer">The custom TEntity EQUALITY Comparer</typeparam>
/// <param name="obj">The object to be looking for</param>
/// <returns></returns>
public Task<bool> ContainsAsync<TEntity, TEntityComparer>(TEntity obj)
where TEntity : class
where TEntityComparer : IEqualityComparer<TEntity>, new()
{
return Task.Factory.StartNew(() => Contains<TEntity, TEntityComparer>(obj));
}
#endregion
#region Insert
/// <summary>
/// Insert one record into the database table
/// </summary>
/// <typeparam name="TEntity">Entity to add into</typeparam>
/// <param name="record">The record to be added</param>
public void Insert<TEntity>(TEntity record) where TEntity : class
{
_Context.Set<TEntity>().Add(record);
}
/// <summary>
/// Asynchronously, Insert one record into the database table
/// </summary>
/// <typeparam name="TEntity">Entity to add into</typeparam>
/// <param name="record">The record to be added</param>
public Task InsertAsync<TEntity>(TEntity record) where TEntity : class
{
return Task.Factory.StartNew(() => Insert(record));
}
/// <summary>
/// Insert a range of reords in a table
/// </summary>
/// <typeparam name="TEntity">Entity to insert into</typeparam>
/// <param name="records">Records to be inserted</param>
public void InsertRange<TEntity>(List<TEntity> records) where TEntity : class
{
_Context.Set<TEntity>().AddRange(records);
}
/// <summary>
/// Asynchronously, Insert a range of reords in a table
/// </summary>
/// <typeparam name="TEntity">Entity to insert into</typeparam>
/// <param name="records">Records to be inserted</param>
public Task InsertRangeAsync<TEntity>(List<TEntity> records) where TEntity : class
{
return Task.Factory.StartNew(() => InsertRange(records));
}
#endregion
#region Delete
/// <summary>
/// Delete One record from a database table
/// </summary>
/// <typeparam name="TEntity">Entity to remove from</typeparam>
/// <param name="record">The record to be removed</param>
public void Delete<TEntity>(TEntity record) where TEntity : class
{
this._Context.Set<TEntity>().Remove(record);
}
/// <summary>
/// Asynchronously, Delete One record from a database table
/// </summary>
/// <typeparam name="TEntity">Entity to remove from</typeparam>
/// <param name="record">The record to be removed</param>
public Task DeleteAsync<TEntity>(TEntity record) where TEntity : class
{
return Task.Factory.StartNew(() => Delete(record));
}
#endregion
/// <summary>
/// Save the repository changes
/// </summary>
public void Save()
{
_Context.SaveChanges();
}
#endregion
#region Disposing
#region Properties
private bool _disposed { get; set; } = false;
#endregion
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_Context.Dispose();
}
}
_disposed = true;
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
In ASP.Net core:
Models
public class Client
{
[Key]
public string ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class Order
{
[Key]
public int ID { get; set; }
public Client CLT { get; set; }
public string PRD { get; set; }
public decimal Total { get; set; }
}
Code First DbContext:
public class TrainContext:DbContext
{
public TrainContext(DbContextOptions<TrainContext> contextOptions) : base(contextOptions)
{
}
public TrainContext()
{
}
protected void OnModelCreating(ModelBuilder builder)
{
foreach (var property in builder.Model.GetEntityTypes().SelectMany(t => t.GetProperties()).Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
property.SetColumnType("decimal(18,2)");
}
}
#region DB Sets
public virtual DbSet<Client> Client { get; set; }
public virtual DbSet<Order> Order { get; set; }
#endregion
}
Client Controller:
public class ClientController : Controller
{
private readonly Repo<TrainContext> db = new Repo<TrainContext>(false);
private readonly TrainContext _context;
public ClientController(TrainContext context)
{
_context = context;
}
public IActionResult Index()
{
//var data = _context.GetAll<Models.Client>();
var data = _context.Client.ToList();
ViewBag.Clients = data;
return View();
}
}
Startup.cs ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<TrainContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DB")));
}
AppSettings.Json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DB": "Server=ServerName; Database=GOrders;Integrated Security=True"
}
}
The problem:
The problem is this repository is work fine when I pass a Db First Context in a Console application.
But when I try to use this repository in my Asp.Net core application with Code first approach I got this error:
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
Please any help to fix this issue ? and massive thanks in advance.
As the error message said:
A provider can be configured by overriding the
'DbContext.OnConfiguring' method or by using 'AddDbContext' on the
application service provider.
Two ways you could meet your requirement.
First way,override the DbContext.OnConfiguring:
public class TrainContext : DbContext
{
public TrainContext(DbContextOptions<TrainContext> contextOptions) : base(contextOptions)
{
}
public TrainContext()
{
}
protected void OnModelCreating(ModelBuilder builder)
{
foreach (var property in builder.Model.GetEntityTypes().SelectMany(t => t.GetProperties()).Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
property.SetColumnType("decimal(18,2)");
}
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=(localdb)\mssqllocaldb; Database=GOrders;Integrated Security=true");
}
#region DB Sets
public virtual DbSet<Client> Client { get; set; }
public virtual DbSet<Order> Order { get; set; }
#endregion
}
Second way by using AddDbContext,but before using AddDbContext,you need add some missing things:
Modify Program.cs:
using Microsoft.AspNetCore.Hosting; //be sure add these references
using Microsoft.Extensions.Hosting;
class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Then modify Sdk="Microsoft.NET.Sdk" to Sdk="Microsoft.NET.Sdk.Web" in your .csproj file as follows:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
</Project>
Detailed reason you could check the following reference:
https://stackoverflow.com/a/58097655/11398810
Startup.cs(Be sure you have Configure method in Startup.cs):
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TrainContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DB")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
After modify like above,then you could run add-migraiton init and update-database command.
I am new to Mock testing and i have created a Unit Test using RhinoMock.
First stub is running proper but second is throwing Null Exception, code is as below.
[TestMethod]
public void TestMethod1()
{
SetUp(); //bind List ftsController and ftsEreceiptNumberPrefix
MockRepository mocks = new MockRepository();
var ftsUnitOfWork = mocks.StrictMock<IFtsUnitOfWork>();
ftsUnitOfWork.Stub(x => x.Repository<FtsController>().Get()).Return(ftsController.AsQueryable());
ftsUnitOfWork.Stub(x =>
{
if (x.Repository<FtsPrefix>() == null)
{
throw new ArgumentNullException("Input cannot be null");
}
x.Repository<FtsPrefix>().Get();
}).Return(ftsPrefix.AsQueryable());
;
BoGeneratePrefix generateEreceiptPrefix = new BoGeneratePrefix (logger,ftsUnitOfWork);
generatePrefix.ProcessJob(null);
}
ProcessJob :
public class BoGeneratePrefix : IBoDataPurging
{
#region private Variables
private readonly ILogger _logger;
private readonly IUnitOfWork _ftsUnitOfWork;
private readonly string _defaultTraceCategory;
#endregion
#region BoGeneratePrefix Constructor
/// <summary>
/// Initialized class oject BoGeneratePrefix
/// </summary>
/// <param name="logger"></param>
/// <param name="ftsUoW"></param>
public BoGeneratePrefix(ILogger logger, IFtsUnitOfWork ftsUoW)
{
_defaultTraceCategory = GetType().Name;
_logger = logger;
_ftsUnitOfWork = ftsUoW;
}
public void ProcessJob(Configuration hconfiguration)
{
try
{
var lstController = _ftsUnitOfWork.Repository<FtsController>().Get().Select(x => x. Id).Distinct().AsNoTracking();
foreach (var Id in lstController.ToList())
{
var prefix = _ftsUnitOfWork.Repository<FtsPrefix>()
.Get(x => x. Id == Id
&& x.Status == Constants.NotPrefix).Count();
var ignoreForLowerSafePoint =
_ftsUnitOfWork.Repository<ConfigurationParameter>()
.Get(y => y.ParamType == Constants.IgnoreForLowerSafePoint &&
y.ParamName == Id)
.AsNoTracking()
.Select(t => t.ParamValues).SingleOrDefault();
var currentTime = DateTime.ParseExact(DateTime.Now.ToString(Constants.HHmmssTime),
Constants.HHmmssTime,
System.Globalization.CultureInfo.InvariantCulture).TimeOfDay;
if ( !( hconfiguration.StartTime <= currentTime
&& hconfiguration.EndTime >= currentTime))
{
return;
}
}
}
catch (Exception ex)
{
throw;
}
}
repository :
public interface IRepository<TEntity> : IDisposable where TEntity : class
{
/// <summary>
/// Delete the record from the database by accepting the input parameter as id
/// </summary>
/// <param name="id">Parameter id is used to delete record from the database</param>
void Delete(object id);
/// <summary>
/// Delete the enity
/// </summary>
/// <param name="entityToDelete"></param>
void Delete(TEntity entityToDelete);
/// <summary>
/// Get Queryable by passing the Expression
/// </summary>
/// <param name="filter">this is used for filter parameter/Expression of query</param>
/// <param name="orderBy">Passing condition of orderBy for query</param>
/// <param name="includeProperties">Properties to be included in to the query while generating the query on filter/order by</param>
/// <returns>Entity type Queryable i.e. creates query depend upon accepted entity type as input parameter</returns>
#pragma warning disable S2360
IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "");
#pragma warning restore S2360
/// <summary>
/// Get Queryable by accepting the input parameter as modifiedSinceTimestamp
/// </summary>
/// <param name="modifiedSinceTimestamp"> modifiedSinceTimestamp </param>
/// <returns></returns>
IQueryable<TEntity> GetModifiedSince(long modifiedSinceTimestamp);
/// <summary>
/// Provides information of an entity by accepting the input parameters
/// </summary>
/// <param name="id">it gives the enity on the id condition </param>
/// <returns>Returns Entity </returns>
TEntity GetById(params object[] id);
/// <summary>
/// Inserts all changes made in this context as a Focus business transaction and enlists it for data insertion.
/// </summary>
/// <param name="entity">Entity i.e. object of generic type </param>
void Insert(TEntity entity);
/// <summary>
/// Gives information about which entity to update
/// </summary>
/// <param name="entityToUpdate">information about to update an enity</param>
void Update(TEntity entityToUpdate);
/// <summary>
///
/// </summary>
/// <param name="query"></param>
/// <param name="parameters"></param>
/// <returns></returns>
IEnumerable<TEntity> ExecWithStoreProcedure(string query, params object[] parameters);
/// <summary>
///
/// </summary>
/// <param name="entityToUpdate"></param>
void Detach(TEntity entityToUpdate);
}
UnitofWork -
public class FtsUnitOfWork : IFtsUnitOfWork
{
#region local variables
private readonly ILogger _logger;
private IFtsDbProvider _ftsDbProvider;
private readonly string _defaultTraceCategory;
private DbContext _context;
private Hashtable _repositories;
#endregion local variables
/// <summary>
/// Constructor for Fts Unit Of Work
/// </summary>
/// <param name="logger"></param>
/// <param name="ftsDbProvider"></param>
public FtsUnitOfWork(ILogger logger, IFtsDbProvider ftsDbProvider)
{
_defaultTraceCategory = GetType().Name;
_logger = logger;
_logger.WriteTrace("FtsUnitOfWork: Initializing", _defaultTraceCategory);
_ftsDbProvider = ftsDbProvider;
_context = new FtsDbContext(_logger, "FtsDbStore", _ftsDbProvider.GetFtsDbCompiledModel());
_logger.WriteTrace("FtsUnitOfWork: Initialized", _defaultTraceCategory);
}
/// <summary>
/// Saves all changes made in this context as a Focus business transaction and enlists it for data consolidation.
/// </summary>
/// <returns></returns>
public virtual int Save()
{
try
{
return _context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
throw new Exception("Optimistic_Concurrency_Check");
}
}
/// <summary>
///
/// </summary>
/// <param name="focusDtTransactionId"></param>
/// <returns></returns>
public int Save(Guid focusDtTransactionId)
{
return _context.SaveChanges();
}
/// <summary>
/// Generic Repository this function creates the Repository by accepting the parameter as Entity type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IRepository<T> Repository<T>() where T : class
{
if (_repositories == null)
{
_repositories = new Hashtable();
}
var type = typeof(T).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(Repository<>);
var repositoryInstance =
Activator.CreateInstance(repositoryType
.MakeGenericType(typeof(T)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IRepository<T>)_repositories[type];
}
/// <summary>
///
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <param name="sqlParameters"></param>
/// <param name="spName"></param>
/// <returns></returns>
public List<T1> GetMany<T1>(SqlParameter[] sqlParameters, string spName)
{
return _context.Database.SqlQuery<T1>(spName, sqlParameters).ToList();
}
/// <summary>
///
/// </summary>
/// <param name="isolationLevel"></param>
/// <returns></returns>
public DbContextTransaction GetDbContextTransaction(IsolationLevel isolationLevel)
{
return _context.Database.BeginTransaction(isolationLevel);
}
#region IDisposable Support
private bool _disposedValue; // To detect redundant calls
/// <summary>
///
/// </summary>
/// <param name="disposing"></param>
public virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_logger.WriteTrace("FtsUnitOfWork: Disposing", _defaultTraceCategory);
_ftsDbProvider = null;
_repositories = null;
this._context.Dispose();
this._context = null;
_logger.WriteTrace("FtsUnitOfWork: Disposed", _defaultTraceCategory);
}
_disposedValue = true;
}
}
/// <summary>
///
/// </summary>
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
If i commented out first stub then second stub work properly otherwise throws below error :
An exception of type 'System.ArgumentNullException' occurred in Fts.Services.Housekeeping.Tests.dll but was not handled in user code
Additional information: Value cannot be null.
i'm trying to bind a model with a IFormFile or IFormFileCollection property to my custom class CommonFile. i have not found so much documentation on internet about it using asp .net core, i tried to follow this link Custom Model Binding in ASP.Net Core 1.0
but it is binding a SimpleType property and i need to bind a complex type. Anyway i tried to make my version of this binding and i've got the following code:
FormFileModelBinderProvider.cs
public class FormFileModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (!context.Metadata.IsComplexType) return null;
var isIEnumerableFormFiles = context.Metadata.ModelType.GetInterfaces().Contains(typeof(IEnumerable<CommonFile>));
var isFormFile = context.Metadata.ModelType.IsAssignableFrom(typeof(CommonFile));
if (!isFormFile && !isIEnumerableFormFiles) return null;
var propertyBinders = context.Metadata.Properties.ToDictionary(property => property,
context.CreateBinder);
return new FormFileModelBinder(propertyBinders);
}
}
FromFileModelBinder.cs
the following code is incomplete because i'm not getting any result with bindingContext.ValueProvider.GetValue(bindingContext.ModelName); while i'm debugging everything is going well until bindingContext.ModelName has no a value and i can't bind my model From httpContext to Strongly typed Models.
public class FormFileModelBinder : IModelBinder
{
private readonly ComplexTypeModelBinder _baseBinder;
public FormFileModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
{
_baseBinder = new ComplexTypeModelBinder(propertyBinders);
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return Task.CompletedTask;
}
}
Any suggestions?
After 10 months i found a solution of that i wanted to do.
In Summary: I Want to replace IFormFile IFormFileCollection with my own classes not attached to Asp .Net because my view models are in different project with poco classes. My custom classes are ICommonFile, ICommonFileCollection, IFormFile (not Asp .net core class) and IFormFileCollection.
i will share it here:
ICommonFile.cs
/// <summary>
/// File with common Parameters including bytes
/// </summary>
public interface ICommonFile
{
/// <summary>
/// Stream File
/// </summary>
Stream File { get; }
/// <summary>
/// Name of the file
/// </summary>
string Name { get; }
/// <summary>
/// Gets the file name with extension.
/// </summary>
string FileName { get; }
/// <summary>
/// Gets the file length in bytes.
/// </summary>
long Length { get; }
/// <summary>
/// Copies the contents of the uploaded file to the <paramref name="target"/> stream.
/// </summary>
/// <param name="target">The stream to copy the file contents to.</param>
void CopyTo(Stream target);
/// <summary>
/// Asynchronously copies the contents of the uploaded file to the <paramref name="target"/> stream.
/// </summary>
/// <param name="target">The stream to copy the file contents to.</param>
/// <param name="cancellationToken">Enables cooperative cancellation between threads</param>
Task CopyToAsync(Stream target, CancellationToken cancellationToken = default(CancellationToken));
}
ICommonFileCollection.cs
/// <inheritdoc />
/// <summary>
/// Represents the collection of files.
/// </summary>
public interface ICommonFileCollection : IReadOnlyList<ICommonFile>
{
/// <summary>
/// File Indexer by name
/// </summary>
/// <param name="name">File name index</param>
/// <returns>File with related file name index</returns>
ICommonFile this[string name] { get; }
/// <summary>
/// Gets file by name
/// </summary>
/// <param name="name">file name</param>
/// <returns>File with related file name index</returns>
ICommonFile GetFile(string name);
/// <summary>
/// Gets Files by name
/// </summary>
/// <param name="name"></param>>
/// <returns>Files with related file name index</returns>
IReadOnlyList<ICommonFile> GetFiles(string name);
}
IFormFile.cs
/// <inheritdoc />
/// <summary>
/// File transferred by HttpProtocol, this is an independent
/// Asp.net core interface
/// </summary>
public interface IFormFile : ICommonFile
{
/// <summary>
/// Gets the raw Content-Type header of the uploaded file.
/// </summary>
string ContentType { get; }
/// <summary>
/// Gets the raw Content-Disposition header of the uploaded file.
/// </summary>
string ContentDisposition { get; }
}
IFormFileCollection.cs
/// <summary>
/// File Collection transferred by HttpProtocol, this is an independent
/// Asp.net core implementation
/// </summary>
public interface IFormFileCollection
{
//Use it when you need to implement new features to Form File collection over HttpProtocol
}
I finally created my model binders successfully, i will share it too:
FormFileModelBinderProvider.cs
/// <inheritdoc />
/// <summary>
/// Model Binder Provider, it inspects
/// any model when the request is triggered
/// </summary>
public class FormFileModelBinderProvider : IModelBinderProvider
{
/// <inheritdoc />
/// <summary>
/// Inspects a Model for any CommonFile class or Collection with
/// same class if exist the FormFileModelBinder initiates
/// </summary>
/// <param name="context">Model provider context</param>
/// <returns>a new Instance o FormFileModelBinder if type is found otherwise null</returns>
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (!context.Metadata.IsComplexType) return null;
var isSingleCommonFile = IsSingleCommonFile(context.Metadata.ModelType);
var isCommonFileCollection = IsCommonFileCollection(context.Metadata.ModelType);
if (!isSingleCommonFile && !isCommonFileCollection) return null;
return new FormFileModelBinder();
}
/// <summary>
/// Checks if object type is a CommonFile Collection
/// </summary>
/// <param name="modelType">Context Meta data ModelType</param>
/// <returns>If modelType is a collection of CommonFile returns true otherwise false</returns>
private static bool IsCommonFileCollection(Type modelType)
{
if (typeof(ICommonFileCollection).IsAssignableFrom(modelType))
{
return true;
}
var hasCommonFileArguments = modelType.GetGenericArguments()
.AsParallel().Any(t => typeof(ICommonFile).IsAssignableFrom(t));
if (typeof(IEnumerable).IsAssignableFrom(modelType) && hasCommonFileArguments)
{
return true;
}
if (typeof(IAsyncEnumerable<object>).IsAssignableFrom(modelType) && hasCommonFileArguments)
{
return true;
}
return false;
}
/// <summary>
/// Checks if object type is CommonFile or an implementation of ICommonFile
/// </summary>
/// <param name="modelType"></param>
/// <returns></returns>
private static bool IsSingleCommonFile(Type modelType)
{
if (modelType == typeof(ICommonFile) || modelType.GetInterfaces().Contains(typeof(ICommonFile)))
{
return true;
}
return false;
}
}
FormFileModelBinder.cs
/// <inheritdoc />
/// <summary>
/// Form File Model binder
/// Parses the Form file object type to a commonFile
/// </summary>
public class FormFileModelBinder : IModelBinder
{
/// <summary>
/// Expression to map IFormFile object type to CommonFile
/// </summary>
private readonly Func<Microsoft.AspNetCore.Http.IFormFile, ICommonFile> _expression;
/// <summary>
/// FormFile Model binder constructor
/// </summary>
public FormFileModelBinder()
{
_expression = x => new CommonFile(x.OpenReadStream(), x.Length, x.Name, x.FileName);
}
/// <inheritdoc />
/// <summary>
/// It Binds IFormFile to Common file, getting the file
/// from the binding context
/// </summary>
/// <param name="bindingContext">Http Context</param>
/// <returns>Completed Task</returns>
// TODO: Bind this context to ICommonFile or ICommonFileCollection object
public Task BindModelAsync(ModelBindingContext bindingContext)
{
dynamic model;
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
var formFiles = bindingContext.ActionContext.HttpContext.Request.Form.Files;
if (!formFiles.Any()) return Task.CompletedTask;
if (formFiles.Count > 1)
{
model = formFiles.AsParallel().Select(_expression);
}
else
{
model = new FormFileCollection();
model.AddRange(filteredFiles.AsParallel().Select(_expression));
}
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Actually Everything is working good except when i have Nested Models. I share an example of my models I'm using and I'll do some comments with working scenarios and which don't
Test.cs
public class Test
{
//It's Working
public ICommonFileCollection Files { get; set; }
//It's Working
public ICommonFileCollection Files2 { get; set; }
//This is a nested model
public TestExtra TestExtra { get; set; }
}
TestExtra.cs
public class TestExtra
{
//It's not working
public ICommonFileCollection Files { get; set; }
}
Actually when i make a request to my API I've got the following (Screenshot):
I'm sharing a screenshot of my postman request too for clarifying my request is good.
If there is any subjection to make this work with nested model it would be great.
UPDATE
Asp Net Core Model Binder won't bind model with one property only, if you have one property in a class, that property will be null but when you add two or more will bind it. my mistake i had one one property in a nested class. The entire code is correct.
I'm working on a client-server system at the moment, and I'm trying to get a collection to synchronise across a websocket. Everything is in C# + .Net 4.5, and I was wondering if there was a particular best practise for synchronising data over a websocket. It's a one way sync:
Server: BindingCollection< MyClass > ----- Websocket -----> Client: BindingCollection< MyClass >
The collection could be up to 1000 objects with 20 fields each so sending the whole lot each time seems a little wasteful.
I would use a observer pattern and only send the changed object to be synced.
So I finally took the time to write a small example.
I am using a in-memory generic repository that invokes events on changes. The changes is then sent to all clients so that you do not have to send the complete list/collection.
A simple model to monitor
using System;
namespace SynchronizingCollection.Common.Model
{
public class MyModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
A Generic Repository
Notice the event OnChange that is called when something is added/updated/removed. The event is "subscribed" to in a XSockets long running controller (a singleton) See the "RepoMonitor" class
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// A static generic thread-safe repository for in-memory storage
/// </summary>
/// <typeparam name="TK">Key Type</typeparam>
/// <typeparam name="T">Value Type</typeparam>
public static class Repository<TK, T>
{
/// <summary>
/// When something changes
/// </summary>
public static event EventHandler<OnChangedArgs<TK,T>> OnChange;
private static ConcurrentDictionary<TK, T> Container { get; set; }
static Repository()
{
Container = new ConcurrentDictionary<TK, T>();
}
/// <summary>
/// Adds or updates the entity T with key TK
/// </summary>
/// <param name="key"></param>
/// <param name="entity"></param>
/// <returns></returns>
public static T AddOrUpdate(TK key, T entity)
{
var obj = Container.AddOrUpdate(key, entity, (s, o) => entity);
if(OnChange != null)
OnChange.Invoke(null,new OnChangedArgs<TK, T>(){Key = key,Value = entity, Operation = Operation.AddUpdate});
return obj;
}
/// <summary>
/// Removes the entity T with key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Remove(TK key)
{
T entity;
var result = Container.TryRemove(key, out entity);
if (result)
{
if (OnChange != null)
OnChange.Invoke(null, new OnChangedArgs<TK, T>() { Key = key, Value = entity, Operation = Operation.Remove});
}
return result;
}
/// <summary>
/// Removes all entities matching the expression f
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static int Remove(Func<T, bool> f)
{
return FindWithKeys(f).Count(o => Remove(o.Key));
}
/// <summary>
/// Find all entities T matching the expression f
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static IEnumerable<T> Find(Func<T, bool> f)
{
return Container.Values.Where(f);
}
/// <summary>
/// Find all entities T matching the expression f and returns a Dictionary TK,T
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static IDictionary<TK, T> FindWithKeys(Func<T, bool> f)
{
var y = from x in Container
where f.Invoke(x.Value)
select x;
return y.ToDictionary(x => x.Key, x => x.Value);
}
/// <summary>
/// Returns all entities as a Dictionary TK,T
/// </summary>
/// <returns></returns>
public static IDictionary<TK, T> GetAllWithKeys()
{
return Container;
}
/// <summary>
/// Returns all entities T from the repository
/// </summary>
/// <returns></returns>
public static IEnumerable<T> GetAll()
{
return Container.Values;
}
/// <summary>
/// Get a single entity T with the key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static T GetById(TK key)
{
return Container.ContainsKey(key) ? Container[key] : default(T);
}
/// <summary>
/// Get a single entity T as a KeyValuePair TK,T with the key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static KeyValuePair<TK, T> GetByIdWithKey(TK key)
{
return Container.ContainsKey(key) ? new KeyValuePair<TK, T>(key, Container[key]) : new KeyValuePair<TK, T>(key, default(T));
}
/// <summary>
/// Checks if the repository has a key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool ContainsKey(TK key)
{
return Container.ContainsKey(key);
}
}
}
Event argument and an enum to know what change just happend
using System;
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// To send changes in the repo
/// </summary>
/// <typeparam name="TK"></typeparam>
/// <typeparam name="T"></typeparam>
public class OnChangedArgs<TK,T> : EventArgs
{
public Operation Operation { get; set; }
public TK Key { get; set; }
public T Value { get; set; }
}
}
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// What kind of change was performed
/// </summary>
public enum Operation
{
AddUpdate,
Remove
}
}
The Controller that send changes to the clients...
using System;
using SynchronizingCollection.Common.Model;
using SynchronizingCollection.Server.Repository;
using XSockets.Core.XSocket;
using XSockets.Core.XSocket.Helpers;
using XSockets.Plugin.Framework;
using XSockets.Plugin.Framework.Attributes;
namespace SynchronizingCollection.Server
{
/// <summary>
/// Long running controller that will send information to clients about the collection changes
/// </summary>
[XSocketMetadata(PluginRange = PluginRange.Internal, PluginAlias = "RepoMonitor")]
public class RepositoryMonitor : XSocketController
{
public RepositoryMonitor()
{
Repository<Guid, MyModel>.OnChange += RepositoryOnChanged;
}
private void RepositoryOnChanged(object sender, OnChangedArgs<Guid, MyModel> e)
{
switch (e.Operation)
{
case Operation.Remove:
this.InvokeTo<Demo>(p => p.SendUpdates, e.Value,"removed");
break;
case Operation.AddUpdate:
this.InvokeTo<Demo>(p => p.SendUpdates, e.Value, "addorupdated");
break;
}
}
}
}
The XSockets controller that clients call to add/remove/update the collection.
using System;
using SynchronizingCollection.Common.Model;
using SynchronizingCollection.Server.Repository;
using XSockets.Core.XSocket;
namespace SynchronizingCollection.Server
{
public class Demo : XSocketController
{
public bool SendUpdates { get; set; }
public Demo()
{
//By default all clients get updates
SendUpdates = true;
}
public void AddOrUpdateModel(MyModel model)
{
Repository<Guid, MyModel>.AddOrUpdate(model.Id, model);
}
public void RemoveModel(MyModel model)
{
Repository<Guid, MyModel>.Remove(model.Id);
}
}
}
And a demo client in C# that adds and removed 10 different objects... But it would be easy to use the JavaScript API as well. Especially with knockoutjs for manipulating the collection on the client.
using System;
using System.Threading;
using SynchronizingCollection.Common.Model;
using XSockets.Client40;
namespace SynchronizingCollection.Client
{
class Program
{
static void Main(string[] args)
{
var c = new XSocketClient("ws://127.0.0.1:4502","http://localhost","demo");
c.Controller("demo").OnOpen += (sender, connectArgs) => Console.WriteLine("Demo OPEN");
c.Controller("demo").On<MyModel>("addorupdated", model => Console.WriteLine("Updated " + model.Name));
c.Controller("demo").On<MyModel>("removed", model => Console.WriteLine("Removed " + model.Name));
c.Open();
for (var i = 0; i < 10; i++)
{
var m = new MyModel() {Id = Guid.NewGuid(), Name = "Person Nr" + i, Age = i};
c.Controller("demo").Invoke("AddOrUpdateModel", m);
Thread.Sleep(2000);
c.Controller("demo").Invoke("RemoveModel", m);
Thread.Sleep(2000);
}
Console.ReadLine();
}
}
}
You can download the project from my dropbox: https://www.dropbox.com/s/5ljbedovx6ufkww/SynchronizingCollection.zip?dl=0
Regards
Uffe
Sorry for the general title but it's a bit hard to explain in few words what is my problem currently.
So I have a simple class factory like this:
public Model Construct<T>(T param) where T : IModelable
{
new Model {Resource = param};
return n;
}
The Model class looks like this:
public class Model
{
public object Resource { get; set; }
}
The problem is, that you can see, is the Resource is currently an object. And I would like that Resource should be the type, what is get from the Construct and not lost the type-safe...
I tried to solve it with type parameter but it fails, because I can extend Model class with type parameter but what if I would like to store it to a simple class repository?
Then Construct will work, but if I would like to get the instanced class from the repository, I have to declare the type paramter again like:
Repository.Get<Model<Spaceship>>(0) .... and of course it's wrong because I would like that Model itself knows, what type of Resource has been added in Construct.
Does anybody any idea how to handle this?
The whole code currently look like this:
/// <summary>
/// Class Repository
/// </summary>
public sealed class Repository
{
/// <summary>
/// The _lock
/// </summary>
private static readonly object _lock = new object();
/// <summary>
/// The _syncroot
/// </summary>
private static readonly object _syncroot = new object();
/// <summary>
/// The _instance
/// </summary>
private static volatile Repository _instance;
/// <summary>
/// The _dict
/// </summary>
private static readonly Dictionary<int, object> _dict
= new Dictionary<int, object>();
/// <summary>
/// Prevents a default data of the <see cref="Repository" /> class from being created.
/// </summary>
private Repository()
{
}
/// <summary>
/// Gets the items.
/// </summary>
/// <value>The items.</value>
public static Repository Data
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null) _instance = new Repository();
}
}
return _instance;
}
}
/// <summary>
/// Allocates the specified id.
/// </summary>
/// <param name="id">The id.</param>
/// <param name="parameter">The parameter.</param>
/// <resource name="id">The id.</resource>
/// <resource name="parameter">The parameter.</resource>
public void Allocate(int id, object parameter)
{
lock (_syncroot)
{
_dict.Add(id, parameter);
}
}
/// <summary>
/// Gets the specified id.
/// </summary>
/// <typeparam name="T">The type of the tref.</typeparam>
/// <param name="id">The id.</param>
/// <returns>``0.</returns>
/// <resource name="id">The id.</resource>
public T Get<T>(int id)
{
lock (_syncroot)
{
return (T) _dict[id];
}
}
}
/// <summary>
/// Class IModelFactory
/// </summary>
public sealed class ModelFactory
{
/// <summary>
/// The _lock
/// </summary>
private static readonly object _lock = new object();
/// <summary>
/// The _instance
/// </summary>
private static volatile ModelFactory _instance;
/// <summary>
/// Prevents a default instance of the <see cref="ModelFactory" /> class from being created.
/// </summary>
private ModelFactory()
{
}
/// <summary>
/// Gets the data.
/// </summary>
/// <value>The data.</value>
public static ModelFactory Data
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null) _instance = new ModelFactory();
}
}
return _instance;
}
}
/// <summary>
/// Constructs the specified param.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="param">The param.</param>
/// <returns>Model{``0}.</returns>
public Model Construct<T>(T param) where T : IModelable
{
var n = new Model {Resource = param};
return n;
}
}
/// <summary>
/// Class Model
/// </summary>
/// <typeparam name="T"></typeparam>
public class Model
{
public object Resource { get; set; }
}
/// <summary>
/// Interface IModelable
/// </summary>
public interface IModelable
{
/// <summary>
/// Gets or sets the mass.
/// </summary>
/// <value>The mass.</value>
float Mass { get; set; }
}
/// <summary>
/// Class spaceship
/// </summary>
public class Spaceship : IModelable
{
/// <summary>
/// Gets or sets the mass.
/// </summary>
/// <value>The mass.</value>
public float Mass { get; set; }
}
So the problem will be lighted here:
Add to the Repository:
Repository.Data.Allocate(1, ModelFactory.Data.Construct(new Spaceship()));
It's okay, but after:
var test_variable = Repository.Data.Get<Model>(1);
So now I have a non type-safe object from a type parameter, I don't know, that what type of class has been stored with the c model construction.
I'm very thankful for the suggestions of using type paramter on the Model class as well, but than it will come up another problem, because I have to change the Get function with it:
var test_variable = Repository.Data.Get<Model<Spaceship>>(1);
But that's definitely wrong, because I won't know, that what kind of type of class has been stored in the model..., so I would like to achieve to avoid this type parameter definition when I would like to load the instance from the Repository.
You can solve this by making your Model class generic, like this:
public class Model<T>
{
public T Resource { get; set; }
}
Then, your Construct method could work like this:
public Model<T> Construct<T>(T param) where T : IModelable<T>
{
return new Model<T>() {Resource = param};
}
You probably need a generic type in the model class:
public class Model<T>
{
public T Resource { get; set; }
}
This sort of structure is one approach you could take:
public Model<T> Construct<T>(T param) where T : IModelable
{
var n = new Model<T> {Resource = param};
return n;
}
public class Model<T> : IModel<T> where T : IModelable
{
public T Resource { get; set; }
}
public interface IModel<out T> where T : IModelable
{
T Resource { get; }
}
This covariant interface allows you to refer to the types more generically where you wish, in the same way that you can pass an IEnumerable<string> into something expecting an IEnumerable<object>.
IModel<Spaceship> shipModel = // whatever
IModel<IModelable> model = shipModel;
//or even:
List<Model<Spaceship>> shipModels = // whatever
IEnumerable<IModel<IModelable>> models = shipModels;