I have a class which implements some interface:
public interface IDb {}
public class DbModel : IDb {}
After this I use Dapper Extensions to insert the object into the DB. This code works well:
var obj = new DbModel();
sqlConnection.Insert(obj);
But when I try insert an instance of this class, casting on the corresponding interface, it gives an Exception:
IDb obj = new DbModel();
sqlConnection.Insert(obj); // exception here
System.ArgumentException: 'No columns were mapped.'
Reason why it not work:
Proceeding from the fact that, DapperExtensions.Insert method is generic, DapperExstensions can't find corresponding map for IDb
Solution:
Create mapper class for IDb and register DbModel properties
public sealed class IDbMapper : ClassMapper<IDb>
{
public IDbMapper()
{
base.Table("TableName");
Map(m => new DbModel().Title);
// and such mapping for other properties
}
}
After this it will be work:
IDb obj = new DbModel();
sqlConnection.Insert(obj);
But here is a problem, when we have many implementations of IDb
interface, in IDbMapper config we can't map columns to corresponding
tables here:
base.Table("TableName");
Map(m => new DbModel().Title);
Because we don't know type of object instance when we do mapping.
Edit:
After some debuging I notice that, in every Insert method call, dapper do mapping, and construct corresponding ClassMapper<> class. We can use this weirdness.
For this we should create SharedState and store in it instance type before calling Insert method.
public static class DapperExstensionsExstensions
{
public static Type SharedState_ModelInstanceType { get; set; }
...
}
IDb obj = new DbModel();
DapperExstensionsExstensions.SharedState_ModelInstanceType = obj.GetType();
sqlConnection.Insert(obj);
After this we can access this property when Dapper will do mapping
public sealed class IDbMapper : ClassMapper<IDb>
{
public IDbMapper()
{
// here we can retrieve Type of model instance and do mapping using reflection
DapperExstensionsExstensions.SharedState_ModelInstanceType
}
}
Whole code snippet:
public interface IDb { }
[MapConfig("TableName", "Schema")]
public class DbTemp : IDb
{
public string Title { get; set; }
}
public class MapConfigAttribute : Attribute
{
public MapConfigAttribute(string name, string schema)
{
Name = name;
Schema = schema;
}
public string Name { get; }
public string Schema { get; }
}
public sealed class DbMapper : ClassMapper<IDb>
{
public DbMapper()
{
DapperExstensionsExstensions.CorrespondingTypeMapper<IDb>((tableName, sechemaName, exprs) =>
{
Table(tableName);
Schema(SchemaName);
return exprs.Select(Map);
});
}
}
public static class DapperExstensionsExstensions
{
private static readonly object _LOCK = new object();
public static Type SharedState_ModelInstanceType { get; set; }
public static List<PropertyMap> CorrespondingTypeMapper<T>(Func<string, string, IEnumerable<Expression<Func<T, object>>>, IEnumerable<PropertyMap>> callback)
{
var tableNameAttribute = (MapConfigAttribute)SharedState_ModelInstanceType.GetCustomAttribute(typeof(MapConfigAttribute));
var tableName = tableNameAttribute.Name;
var schemaName = tableNameAttribute.Schema;
var result = callback(tableName, schemaName, new GetPropertyExpressions<T>(SharedState_ModelInstanceType));
Monitor.Exit(_LOCK);
return result.ToList();
}
public static object Insert<TInput>(this IDbConnection connection, TInput entity) where TInput : class
{
Monitor.Enter(_LOCK);
SharedState_ModelInstanceType = entity.GetType();
return DapperExtensions.DapperExtensions.Insert(connection, entity);
}
}
public class GetPropertyExpressions<TInput> : IEnumerable<Expression<Func<TInput, object>>>
{
private readonly Type _instanceType;
public GetPropertyExpressions(Type instanceType)
{
_instanceType = instanceType;
}
public IEnumerator<Expression<Func<TInput, object>>> GetEnumerator()
{
return _instanceType
.GetProperties()
.Select(p => new GetPropertyExpression<TInput>(_instanceType, p.Name).Content()).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class GetPropertyExpression<TInput> : IContent<Expression<Func<TInput, object>>>
{
private readonly Type _instanceType;
private readonly string _propertyName;
public GetPropertyExpression(Type instanceType, string propertyName)
{
_instanceType = instanceType;
_propertyName = propertyName;
}
public Expression<Func<TInput, object>> Content()
{
// Expression<Func<IDb, object>> :: model => (object)(new DbModel().Title)
var newInstance = Expression.New(_instanceType);
var parameter = Expression.Parameter(typeof(TInput), "model");
var getPropertyExpression = Expression.Property(newInstance, _propertyName);
var convertedProperty = Expression.Convert(getPropertyExpression, typeof(object));
var lambdaExpression = Expression.Lambda(convertedProperty, parameter);
return (Expression<Func<TInput, object>>)lambdaExpression;
}
}
It works for me
IDb obj = new DbModel();
sqlConnection.Insert(obj);
Related
I have a strange issue.
I am creating a generic class and I have a generic method and test. The test looks like this:
[Test]
public async Task ReturnGeneric()
{
// Assemble
const int id = 1;
var request = new GetGeneric<Venue>(id);
var services = GetGenericContext.GivenServices();
var handler = services.WhenCreateHandler();
var venues = Builder<Venue>.CreateListOfSize(20).Build().ToDbSet();
services.DatabaseContext.Set<Venue>().Returns(venues);
// Act
var response = await handler.Handle(request, CancellationToken.None);
// Assert
response.Success.Should().BeTrue();
response.Error.Should().BeNull();
response.Result.Should().BeOfType<Venue>();
}
}
And the method looks like this:
public async Task<Attempt<T>> Handle(GetGeneric<T, TKey> request, CancellationToken cancellationToken)
{
var id = request.Id;
if (EqualityComparer<TKey>.Default.Equals(id, default)) return ValidationError.Required(nameof(id));
var generics = _databaseContext.Set<T>().AsQueryable();
var t = _databaseContext.Set<T>().ToList();
var generic = generics.SingleOrDefault(m => m.Id.Equals(request.Id));
var x = t.SingleOrDefault(m => m.Id.Equals(id));
if (generic == null) return NotFoundError.ItemNotFound(nameof(T), request.Id.ToString());
return generic;
}
variables t and x are just tests for my own sanity.
The issue here is that generic is null in my test, but x is not.
It seems to have a problem with the AsQueryable() method. For some reason if I do a call to AsQueryable() there are no results in the collection, but there are if it invoke ToList().
This is my extension method for ToDbSet():
public static class DbSetExtensions
{
public static DbSet<T> ToDbSet<T>(this IEnumerable<T> data) where T : class
{
var queryData = data.AsQueryable();
var dbSet = Substitute.For<DbSet<T>, IQueryable<T>>();
((IQueryable<T>)dbSet).Provider.Returns(queryData.Provider);
((IQueryable<T>)dbSet).Expression.Returns(queryData.Expression);
((IQueryable<T>)dbSet).ElementType.Returns(queryData.ElementType);
((IQueryable<T>)dbSet).GetEnumerator().Returns(queryData.GetEnumerator());
return dbSet;
}
}
Can anyone think of a reason why this is not working?
The entire class looks like this:
public class GenericGet<T, TKey> : IRequest<Attempt<T>> where T: TClass<TKey>
{
public TKey Id { get; }
public GenericGet(TKey id)
{
Id = id;
}
}
public class GenericGet<T> : GenericGet<T, int> where T : TClass<int>
{
public GenericGet(int id) : base(id)
{
}
}
public class GenericGetHandler<T, TKey> : IRequestHandler<GenericGet<T, TKey>, Attempt<T>> where T: TClass<TKey>
{
private readonly DatabaseContext _databaseContext;
public GenericGetHandler(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public async Task<Attempt<T>> Handle(GenericGet<T, TKey> request, CancellationToken cancellationToken)
{
var id = request.Id;
if (EqualityComparer<TKey>.Default.Equals(id, default)) return ValidationError.Required(nameof(id));
var generics = _databaseContext.Set<T>().AsQueryable();
var generic = generics.SingleOrDefault(m => m.Id.Equals(request.Id));
if (generic == null) return NotFoundError.ItemNotFound(nameof(T), request.Id.ToString());
return generic;
}
}
public class GenericGetHandler<T> : GenericGetHandler<T, int> where T : TClass<int>
{
public GenericGetHandler(DatabaseContext databaseContext) : base(databaseContext)
{
}
}
And a venue looks like this:
public class Venue: TClass<int>
{
[Required, MaxLength(100)] public string Name { get; set; }
[MaxLength(255)] public string Description { get; set; }
public IList<Theatre> Theatres { get; set; }
}
I changed my DatabaseContext mock to actually use the in memory provider as suggested by Fabio.
It looks like this:
public class DatabaseContextContext
{
public DatabaseContext DatabaseContext;
protected DatabaseContextContext()
{
var options = new DbContextOptionsBuilder<DatabaseContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.EnableSensitiveDataLogging()
.Options;
DatabaseContext = new DatabaseContext(options);
}
}
And then my GetGenericContext looks like this:
public class GenericGetContext<T> : DatabaseContextContext where T : TClass<int>
{
public static GenericGetContext<T> GivenServices() => new GenericGetContext<T>();
public GenericGetHandler<T> WhenCreateHandler() => new GenericGetHandler<T>(DatabaseContext);
}
Instead of doing:
services.DatabaseContext.Set<Venue>().Returns(venues);
We are not mocking anymore, so we can actually do:
services.DatabaseContext.Venues.AddRange(venues);
services.DatabaseContext.SaveChanges();
We must call save changes otherwise it won't actually say the venues to the collection.
Once you do that, everything works as expected.
I'm trying to get every child of Commands in multiple assembly's to store them in a List but in order to do that i need to create an instance of that child in order to store it but so i am trying to use Activator.CreateInstance the goal is to have a ctor public for outside usage and a ctor for the Activator so it can create instances to store but, problem is that Activator just can't find the ctor for some reason, i even tagged the ctor as public but no luck
public abstract class Command
{
public static List<Command> List { get; set; }
public static Dictionary<Type, int> Lookup { get; set; }
public Command(int id, FieldInfo[] field)
{
Id = id;
Fields = field;
}
public Command()
{
Command command = List[Lookup[GetType()]];
Id = command.Id;
Fields = command.Fields;
}
public static void Initialize()
{
Lookup = new Dictionary<Type, int>();
List = new List<Command>();
foreach (Type type in
AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes())
.Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(Command))))
{
Command command = (Command)Activator.CreateInstance(type, List.Count, type.GetFields());
Lookup.Add(type, command.Id);
List.Add(command);
}
}
}
public class PlayerMove : Command
{
}
[TestClass()]
public class PacketTests
{
[TestMethod()]
public void PackTest()
{
Command.Initialize();
Packet packet = new Packet();
var cmd = new PlayerMove()
{
};
cmd.Send(Method.Unreliable);
var g = Command.List;
}
What am i doing wrong ?
You are trying to construct an instance of PlayerMove. This only has a default constructor, i.e. PlayerMove(), which will call the base constructor Command(). Therefore your call to Activator.CreateInstance should look like
Command command = (Command)Activator.CreateInstance(type);
Alternatively you should add an additional constructor to the PlayerMove class:
public class PlayerMove : Command
{
public PlayerMove(int id, FieldInfo[] field) : base(id, field){}
public PlayerMove() : base(){}
}
But I think that probably what you actually want is something like this:
public abstract class Command
{
public static List<Command> List { get; private set; }
public static Dictionary<Type, int> Lookup { get; private set; }
public int Id { get; }
public FieldInfo[] Fields { get; }
protected Command(int id, FieldInfo[] field)
{
Id = id;
Fields = field;
}
protected Command()
{
Command command = List[Lookup[GetType()]];
Id = command.Id;
Fields = command.Fields;
}
public static void Initialize()
{
Lookup = new Dictionary<Type, int>();
List = new List<Command>();
foreach (Type type in
AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes())
.Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(Command))))
{
Command command = (Command) Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {List.Count, type.GetFields()}, null);
Lookup.Add(type, command.Id);
List.Add(command);
}
}
}
public class PlayerMove : Command
{
private PlayerMove(int id, FieldInfo[] field) : base(id, field)
{
}
public PlayerMove()
{
}
}
This adds a private constructor to the PlayerMove command that is called through reflection and populates the Id and Fields properties on the base class, and a paramaterless constructor that can then be used by other clients of the class.
I am using Strategy Pattern, I have heaps of rules and I need to check all rows in Azure storage table against each Rule.
interface IRule where TEntity : TableEntity, new()
{
string TableName { get; } // It could be "ContractAccount", "Bill", "Transaction" etc.
string Rule { get; }
string SaveToTable { get; }
TableQuery<TEntity> TableQuery { get; }
ReportEntity Handle(TableEntity entity);
}
So instance of rules lives inside the Validator.
public Validator()
{
Rules = new List<IRule>();
Rules.Add(new AddressRule());
}
The Table Entity class(ContractAccount.cs Bill.cs etc.) will have the same name as the value IRule.TableName holds.
So this is where the ContractAccount comes from.
Then in the Validator, I have Validate() which looks like:
public async void Validate(CloudStorageAccount storageAccount)
{
var tableClient = storageAccount.CreateCloudTableClient();
//.....
var query = new TableQuery<ContractAccount>(); //<-- I want to replace ContractAccount with something generic
//...
var rows = await tableToBeValidated.ExecuteQuerySegmentedAsync(query, token);
}
//...
}
In my AddressRule.cs
public class AddressRule : IRule<ContractAccount>
{
public string TableName => "ContractAccount";
public string Rule => "Email cannot be empty";
public string SaveToTable => "XXXX";
public TableQuery<ContractAccount> TableQuery => new TableQuery<ContractAccount>();
public ReportEntity Handle(TableEntity entity)
{
var contract = entity as ContractAccount;
if(contract == null)
{
throw new Exception($"Expecting entity type {TableName}, but passed in invalid entity");
}
if (string.IsNullOrWhiteSpace(contract.Address))
{
var report = new ReportEntity(this.Rule, contract.UserId, contract.AccountNumber, contract.ContractNumber)
{
PartitionKey = contract.UserId,
RowKey = contract.AccountNumber
};
return report;
}
return null;
}
}
As you can see
var query = new TableQuery<ContractAccount>();
I need to replace the Hard-coded with something like:
var type = Type.GetType(tableName);
var query = new TableQuery<type>();
but the placeholder(ContractAccount) will change when app is running, it could be Bill, Policy, Transaction etc....
I cannot use the <T> thing.
How can I replace the ContractAccount with a generic thing?
Update 2
After applied Juston.Another.Programmer's suggection, I got this error.
Update 3
Now I updated code to below:
interface IRule<TEntity> where TEntity : TableEntity
{
string TableName { get; }
string Rule { get; }
string SaveToTable { get; }
ReportEntity Handle(TableEntity entity);
TableQuery<TEntity> GetTableQuery();
}
Which I specified what type of class the TEntity has to be, it removes the 1st error, but the 2nd error persists:
Error CS0310 'TEntity' must be a non-abstract type with a public
parameterless constructor in order to use it as parameter 'TElement'
in the generic type or method 'TableQuery'
Update 4
I found how to fix the another error:
interface IRule<TEntity>
where TEntity : TableEntity, new()
But then, I have problem to add my AddressRule into Rules in the Validator class.
public Validator()
{
Rules = new List<IRule<TableEntity>>();
var addressRule = new AddressRule();
Rules.Add(addressRule);
}
Something like this:
var genericType = typeof(TableQuery<>);
Type[] itemTypes = { Type.GetType("MyNamespace.Foo.Entities." + tableName) };
var concretType = genericType.MakeGenericType(itemTypes);
var query = Activator.CreateInstance(concretType);
You could use reflection like #Christoph suggested, but in this case there's an easier approach. Add a TEntity generic parameter to your IRule class instead of using the TableName string property and add a GetTableQuery method to the class.
interface IRule<TEntity>
{
string Rule { get; }
string SaveToTable { get; }
ReportEntity Handle(TableEntity entity);
TableQuery<TEntity> GetTableQuery();
}
Then, in your IRule<TEntity> implementations add the correct entity. Eg for AddressRule.
public class AddressRule : IRule<ContractAcccount>
{
public string TableName => "ContractAccount";
public string Rule => "Email cannot be empty";
public string SaveToTable => "XXXX";
public ReportEntity Handle(TableEntity entity)
{
var contract = entity as ContractAccount;
if(contract == null)
{
throw new Exception($"Expecting entity type {TableName}, but passed in invalid entity");
}
if (string.IsNullOrWhiteSpace(contract.Address))
{
var report = new ReportEntity(this.Rule, contract.UserId, contract.AccountNumber, contract.ContractNumber)
{
PartitionKey = contract.UserId,
RowKey = contract.AccountNumber
};
return report;
}
return null;
}
public TableQuery<ContractAccount> GetTableQuery()
{
return new TableQuery<ContractAccount>();
}
}
Now, in your Validate method, you can use the GetTableQuery method from the IRule.
public async void Validate(CloudStorageAccount storageAccount)
{
var tableClient = storageAccount.CreateCloudTableClient();
//.....
var query = rule.GetTableQuery();
//...
var rows = await tableToBeValidated.ExecuteQuerySegmentedAsync(query, token);
}
//...
}
The longer I think about it the more I get the feeling that what you need is a generic solution and not one with generics. I guess that the table client in line
var tableClient = storageAccount.CreateCloudTableClient();
does always return something like a DataTable or an object with an IEnumerable, independently of whether you ask for a ContractAccount or a Bill. If that's the case, it might be better to have a validator that loads all the rules of all entities from the database (or through factory patterns or hardcoded) and then applies the according ones to the given entity.
Like that, the set of rules can be defined using XML or some other sort of serialization (not part of this example) and only a few rule classes are needed (I call them EntityValidationRule).
The parent of all rules for all entities could look like this:
public abstract class EntityValidationRule {
//Private Fields
private Validator validator;
//Constructors
public EntityValidationRule(String tableName, IEnumerable<String> affectedFields) {
TableName = tableName ?? throw new ArgumentNullException(nameof(tableName));
AffectedFields = affectedFields?.ToArray() ?? Array.Empty<String>();
}
//Public Properties
public String TableName { get; }
public String[] AffectedFields { get; }
public virtual String Description { get; protected set; }
//Public Methods
public Boolean IsValid(DataRow record, ref IErrorDetails errorDetails) {
if (record == null) throw new InvalidOperationException("Programming error in Validator.cs");
if (!Validator.IdentifyerComparer.Equals(record.Table.TableName, TableName)) throw new InvalidOperationException("Programming error in Validator.cs");
String myError = GetErrorMessageIfInvalid(record);
if (myError == null) return true;
errorDetails = CreateErrorDetails(record, myError);
return false;
}
//Protected Properties
public Validator Validator {
get {
return validator;
}
internal set {
if ((validator != null) && (!Object.ReferenceEquals(validator, value))) {
throw new InvalidOperationException("An entity validation rule can only be added to a single validator!");
}
validator = value;
}
}
//Protected Methods
protected virtual IErrorDetails CreateErrorDetails(DataRow record, String errorMessage) {
return new ErrorDetails(record, this, errorMessage);
}
protected abstract String GetErrorMessageIfInvalid(DataRow record);
}
and to stay with your example, the sample implementation for an empty text field check could look like this (having an intermediate class OneFieldRule):
public abstract class OneFieldRule : EntityValidationRule {
public OneFieldRule(String tableName, String fieldName) : base(tableName, new String[] { fieldName }) {
}
protected String FieldName => AffectedFields[0];
}
and like this:
public class TextFieldMustHaveValue : OneFieldRule {
public TextFieldMustHaveValue(String tableName, String fieldName) : base(tableName, fieldName) {
Description = $"Field {FieldName} cannot be empty!";
}
protected override String GetErrorMessageIfInvalid(DataRow record) {
if (String.IsNullOrWhiteSpace(record.Field<String>(FieldName))) {
return Description;
}
return null;
}
}
Then the central validator that works like a service to validate whatever entity needs to be validated I might implement like this:
public sealed class Validator {
//Private Fields
private Dictionary<String, List<EntityValidationRule>> ruleDict;
//Constructors
//The list of all rules we just have somehow...
public Validator(IEnumerable<EntityValidationRule> rules, StringComparer identifyerComparer) {
if (rules == null) throw new ArgumentNullException(nameof(rules));
if (identifyerComparer == null) identifyerComparer = StringComparer.OrdinalIgnoreCase;
IdentifyerComparer = identifyerComparer;
ruleDict = new Dictionary<String, List<EntityValidationRule>>(IdentifyerComparer);
foreach (EntityValidationRule myRule in rules) {
myRule.Validator = this;
List<EntityValidationRule> myRules = null;
if (ruleDict.TryGetValue(myRule.TableName, out myRules)) {
myRules.Add(myRule);
} else {
myRules = new List<EntityValidationRule> { myRule };
ruleDict.Add(myRule.TableName, myRules);
}
}
}
//Public Properties
public StringComparer IdentifyerComparer { get; }
//Public Methods
public Boolean IsValid(DataRow record, ref IErrorDetails[] errors) {
//Check whether the record is null
if (record == null) {
errors = new IErrorDetails[] { new ErrorDetails(record, null, "The given record is null!") };
return false;
}
//Loop through every check and invoke them
List<IErrorDetails> myErrors = null;
IErrorDetails myError = null;
foreach (EntityValidationRule myRule in GetRules(record.Table.TableName)) {
if (myRule.IsValid(record, ref myError)) {
if (myErrors == null) myErrors = new List<IErrorDetails>();
myErrors.Add(myError);
}
}
//Return true if there are no errors
if (myErrors == null) return true;
//Otherwise assign them as result and return false
errors = myErrors.ToArray();
return false;
}
//Private Methods
private IEnumerable<EntityValidationRule> GetRules(String tableName) {
if (ruleDict.TryGetValue(tableName, out List<EntityValidationRule> myRules)) return myRules;
return Array.Empty<EntityValidationRule>();
}
}
And the error details as an interface:
public interface IErrorDetails {
DataRow Entity { get; }
EntityValidationRule Rule { get; }
String ErrorMessage { get; }
}
...and an implementation of it:
public class ErrorDetails : IErrorDetails {
public ErrorDetails(DataRow entity, EntityValidationRule rule, String errorMessage) {
Entity = entity;
Rule = rule;
ErrorMessage = errorMessage;
}
public DataRow Entity { get; }
public EntityValidationRule Rule { get; }
public String ErrorMessage { get; }
}
I know this is a totally different approach as you started off, but I think the generics give you a hell of a lot of work with customized entities that have customized validators for each and every table in your database. And as soon as you add a table, code needs to be written, compiled and redistributed.
IQueryable extension method is fine in normal method, but not within a generic method.
Compile error: IQueryable does not contain a definition for MyExtension and the best extension method overload DataExtensions.MyExtension(IQueryable Fred) requires a receiver of type IQueryable Fred
Goal is turn the // do something interesting below into a generic method that would work acrross all FlintstoneObject types.
public static class RepetitiveCodeBelow
{
public static int RepetitiveCode()
{
var count = 0;
using (var context = new DataContext())
{
foreach (var data in context.Freds.AsNoTracking().Where(item => item.PrimaryKey > 0).MyExtension())
{
// do something interesting
}
foreach (var data in context.Barnies.AsNoTracking().Where(item => item.PrimaryKey > 0).MyExtension())
{
// do something interesting
}
// more types omitted
}
return count;
}
}
Working version:
public List<Fred> GetFredList()
{
using (var context = new DataContext())
{
return context.Freds.AsNoTracking().MyExtension().ToList();
}
}
Wont compile:
public List<T> GetList<T>() where T : FlintstoneObject<T>, new()
{
using (var context = new DataContext())
{
return context.Set<T>().AsNoTracking().MyExtension().ToList();
}
}
Full sample
public abstract class FlintstoneObject<T> where T : class
{
public abstract int PrimaryKey { get; set; }
}
public class Fred : FlintstoneObject<Fred>
{
public override int PrimaryKey { get; set; }
}
public class Barny : FlintstoneObject<Barny>
{
public override int PrimaryKey { get; set; }
}
public static class DataExtensions
{
public static IQueryable<Fred> MyExtension(this IQueryable<Fred> queryable)
{
return queryable;
}
}
public class DataContext : DbContext
{
public DbSet<Fred> Freds { get; set; }
public DbSet<Barny> Barnies { get; set; }
public List<Fred> GetFredList()
{
using (var context = new DataContext())
{
return context.Freds.AsNoTracking().MyExtension().ToList();
}
}
public List<T> GetList<T>() where T : FlintstoneObject<T>, new()
{
using (var context = new DataContext())
{
return context.Set<T>().AsNoTracking().MyExtension().ToList();
}
}
}
Your code is probably lacking of this extension:
public static IQueryable<T> MyExtension<T>(this IQueryable<T> queryable)
{
return queryable;
}
to make it compilable.
Anyway, what you intended to do with your generic stuff looks strange:
public class Fred : FlintstoneObject<Fred>
I thought T should be some class other than Fred which is inheriting the generic type FlintstoneObject<T>.
I have the following C# code. Here the validations are kept outside the class to satisfy Open – Closed Principle. This is working fine. But the challenge is – the validations are not generic. It is specific to employee class (E.g DateOfBirthRuleForEmployee). How do I make the validations generic for all objects (DateOfBirthRuleForAnyObject).
Note: Make Generic <==> Make Type-Independent
Note: I have NameLengthRuleForEmployee validation also. New validation may come in future.
EDIT
Generic Method Example: Using “OfType” in LINQ
CODE
class Program
{
static void Main(string[] args)
{
Employee employee = new Employee();
employee.DateOfBirth = DateTime.Now;
employee.Name = "Lijo";
DateOfBirthRuleForEmployee dobRule = new
DateOfBirthRuleForEmployee();
NameLengthRuleForEmployee nameRule = new
NameLengthRuleForEmployee();
EmployeeManager employeeManager = new EmployeeManager();
employeeManager.AddRules(dobRule);
employeeManager.AddRules(nameRule);
bool result = employeeManager.validateEntity(employee);
Console.WriteLine(result);
Console.ReadLine();
}
}
public interface IEntity
{
}
public interface IRule<TEntity>
{
bool IsValid(TEntity entity);
}
public class DateOfBirthRuleForEmployee : IRule<Employee>
{
public bool IsValid(Employee entity)
{
return (entity.DateOfBirth.Year <= 1975);
}
}
public class NameLengthRuleForEmployee : IRule<Employee>
{
public bool IsValid(Employee employee)
{
return (employee.Name.Length < 5);
}
}
public class Employee : IEntity
{
private DateTime dateOfBirth;
private string name;
public DateTime DateOfBirth
{
get
{
return dateOfBirth;
}
set
{
dateOfBirth = value;
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
}
public class EmployeeManager
{
RulesEngine<Employee> engine = new RulesEngine<Employee>();
public void AddRules(IRule<Employee> rule)
{
engine.AddRules(rule);
//engine.AddRules(new NameLengthRuleForEmployee());
}
public bool validateEntity(Employee employee)
{
List<IRule<Employee>> rulesList = engine.GetRulesList();
//No need for type checking. Overcame Invariance problem
bool status = true;
foreach (IRule<Employee> theRule in rulesList)
{
if (!theRule.IsValid(employee))
{
status = false;
break;
}
}
return status;
}
}
public class RulesEngine<TEntity> where TEntity : IEntity
{
private List<IRule<TEntity>> ruleList = new
List<IRule<TEntity>>();
public void AddRules(IRule<TEntity> rule)
{
//invariance is the key term
ruleList.Add(rule);
}
public List<IRule<TEntity>> GetRulesList()
{
return ruleList;
}
}
The challange is for your rules to know which property of what type to validate. You can either provide this by implementing an interface that provides just that as suggested by SLaks or by quessing it dynamically or by providing a concrete rule class with a bit more information on how to access the given property, e.g.:
class NameRule<T> : IRule<T>
{
private Func<T, string> _nameAccessor;
public NameRule(Func<T, string> nameAccessor)
{
_nameAccessor = nameAccessor;
}
public bool IsValid(T instance)
{
return _nameAccessor(instance).Length > 10;
}
}
this ofcourse can be used in the following way:
NameRule<Employee> employeeNameRule = new NameRule<Employee>(x => x.name);
employeeManager.addRule(employeeNameRule);