Assume I have a database with products:
Id
Name
MaxSpeed
40
Lite
100
41
Basic
500
42
Premium
1000
And assume we sell services based on these products. These services can be configured for a specific speed up to the associated product's maximum speed. So when I subscribe to a basic service, I am allowed to put any values between 0 and 500 for my speed configuration.
public record Product(int Id, string Name, int MaxSpeed);
public record Service(string Name, int ProductId, ServiceConfiguration Configuration);
public record ServiceConfiguration(int Speed, int Other, bool Options, float Whatever);
I also have a repository class to provide access to my database:
public interface IProductRepository
{
Product GetProductById(int id);
}
So now I want to implement validation. I start with a ServiceValidator:
public class ServiceValidator : AbstractValidator<Service>
{
public ServiceValidator(IProductRepository productRepository)
{
RuleFor(s => s.Name).MaximumLength(50);
RuleFor(s => s.Configuration)
.SetValidator(c => new ServiceConfigurationValidator(productRepository));
}
}
I have a constructor here that takes an IProductRepository, which is kinda weird since it doesn't really use it, it just needs to pass it on the the ServiceConfigurationValidator. Suggestions on how to improve this are welcome.
Then on to my actual question: Let's say I want to validate the following service:
{
"Id": 42,
"Name": "My subscription Foo Bar",
"Configuration": {
"Speed": 10,
"Other": "Foo",
"Options": "Bar"
}
}
So I start implementing a ServiceConfigurationValidator:
public class ServiceConfigurationValidator : AbstractValidator<ServiceConfiguration>
{
public ServiceConfigurationValidator(IProductRepository productRepository)
{
// Use database to retrieve product 42, get it's associated max. speed and ensure the
// serviceconfiguration value is equal to, or less than, the products max. speed.
}
}
How would I (re)write this ServiceConfigurationValidator so that:
I can access the ProductId from the "parent" Service
I can use that ProductId to retrieve the product from the database using the IProductRepository and check that the Configuration.Speed doesn't exceed the product's MaxSpeed?
I've looked into PropertyValidators, gone through Fluentvalidation's documentation but I can't for the life of me find anything that points me in the correct direction.
Writing a Custom Validator is what you need.
Approach 1: With .Must()
RuleFor(s => s.Configuration)
.Must((parent, config, context) =>
{
Product product = productRepository.GetProductById(parent.ProductId);
if (product == null)
{
// Handle error for product not existed (if needed)
context.AddFailure("Product is not existed!");
return false;
}
context.MessageFormatter.AppendArgument("MaxSpeed", product.MaxSpeed);
return config.Speed <= product.MaxSpeed;
})
.WithMessage("{PropertyName} must be lesser than or equal to {MaxSpeed}.");
Approach 2: With .Custom()
RuleFor(s => s.Configuration)
.Custom((config, context) =>
{
Product product = productRepository.GetProductById(context.InstanceToValidate.ProductId);
if (product == null)
{
// Handle error for product not existed (if needed)
context.AddFailure("Product is not existed!");
return;
}
if (config.Speed > product.MaxSpeed)
{
context.MessageFormatter.AppendArgument("PropertyName", nameof(config.Speed));
context.MessageFormatter.AppendArgument("MaxSpeed", product.MaxSpeed);
context.AddFailure("{PropertyName} must be lesser than or equal to {MaxSpeed}.");
return;
}
});
Demo (Approach 1 & 2) # .NET Fiddle
I don't think that you need to build the custom validator with the PropertyValidator abstract class as it is designed for complexity and generic purpose. The above approaches should be enough for your use case.
If you are still keen to implement a custom validator in a separate class, your custom validator class have to inherit the PropertyValidator<T, TElement> abstract class.
Approach 3: With PropertyValidator<T, TElement>
RuleFor(s => s.Configuration)
.SetValidator(new ServiceConfigurationValidator(productRepository));
public class ServiceConfigurationValidator : PropertyValidator<Service, ServiceConfiguration>
{
private readonly IProductRepository _productRepository;
public ServiceConfigurationValidator(IProductRepository productRepository)
{
_productRepository = productRepository; ;
}
public override bool IsValid(ValidationContext<Service> context, ServiceConfiguration value)
{
Product product = _productRepository.GetProductById(context.InstanceToValidate.ProductId);
if (product == null)
{
// Handle error for product not existed (if needed)
context.AddFailure("Product is not existed!");
return false;
}
if (value.Speed > product.MaxSpeed)
{
context.MessageFormatter.AppendArgument("PropertyName", nameof(value.Speed));
context.MessageFormatter.AppendArgument("MaxSpeed", product.MaxSpeed);
context.AddFailure("{PropertyName} must be lesser than or equal to {MaxSpeed}.");
return false;
}
return true;
}
public override string Name => "ServiceConfigurationValidator";
}
Demo (Approach 3) # .NET Fiddle
Related
Using .NET Core 3.1 with EF Core 3.1
Considering those 3 nested DTO :
public class AdminImmoOrderDto
{
public IQueryable<AdminImmoOrderProductDto> OrderProducts { get; set; }
public bool IsCompleted { get; set; }
}
public class AdminImmoOrderProductDto
{
public IQueryable<AdminImmoOrderProductFileDto> OrderFiles { get; set; }
public bool IsCompleted { get; set; }
}
public class AdminImmoOrderProductFileDto
{
public string FileName { get; set; }
public string FileUrl { get; set; }
}
An Order contains some OrderProducts and those OrderProducts contain some OrderFiles. The logic is as following : to be IsCompleted an OrderProduct must have at least one OrderFile.
I have written some extension method to do the mapping and projection logic, like this :
public static IQueryable<AdminImmoOrderProductDto> MapOrderProductToDto(this ICollection<OrderProducts> products)
{
return products
.AsQueryable()
.Select(p => new AdminImmoOrderProductDto
{
IsCompleted = p.OrderFiles.Any(of => of != null),
OrderFiles = p.OrderFiles.MapOrderProductFileToDto()
});
}
}
Up until there, everything is working. But as you can see I'm using some method to map the children collection inside the parent mapping method.
With that in mind, when trying to set IsCompleted for the top level DTO OrderDto, I need to filter on the child property OrderProductDto.IsCompleted. This time, all the OrderProducts have to be completed for the parent Order to be considered completed aswell. I need to access IsCompleted in the children collection but I can't so I'm trying this :
public static IQueryable<AdminImmoOrderDto> MapOrderToDto(this IQueryable<Orders> orders)
{
return orders
.Include(order => order.OrderProducts)
.ThenInclude(orderProduct => orderProduct.OrderFiles)
.Select(o => new AdminImmoOrderDto
{
OrderProducts = o.OrderProducts.MapOrderProductToDto(),
IsCompleted = o.OrderProducts.MapOrderProductToDto().All(od => od.IsCompleted == true)
});
}
}
But this line
IsCompleted = o.OrderProducts.MapOrderProductToDto().All(od => od.IsCompleted == true)
Throws the following EF Core exception
.All(od => od.IsCompleted == True)' by 'NavigationExpandingExpressionVisitor' failed.
This may indicate either a bug or a limitation in EF Core.
See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
And, has you can see the link refers to Client vs. Server Evaluation
This property IsCompleted is there to later filter my Orders. I've tried moving this piece of code in my filter without success. I have also tried to do this logic before mapping my DTO, using the linq code bloc instead of the property in the child DTO (since it does not exist before the mapping is done).
I also tried explicitly forcing the client evaluation by calling .ToList() or .AsEnumerable() before calling .All()
My question is : how can I set the IsCompleted property for OrderDto depending on the IsCompleted property in the child Dto OrderProductDto without flattening my top level IQueryable ?
Thanks you.
Update 10/03
I've tried moving this to my filter Query Object, i don't need the property if i can filter on the children of on order. Sadly the error is still the same. See code below.
public enum OrdersFilterBy
{
NoFilter = 0,
IsCompleted = 1,
}
public static class AdminImmoOrderListDtoFilter
{
public static IQueryable<AdminImmoOrderDto> FilterOrdersBy(this IQueryable<AdminImmoOrderDto> orders, OrdersFilterBy filterBy)
{
switch(filterBy)
{
case OrdersFilterBy.NoFilter:
return orders;
case OrdersFilterBy.IsCompleted:
return orders.Where(o => o.OrderProducts.AsEnumerable().All(op => op.IsCompleted == true));
default:
throw new ArgumentOutOfRangeException
(nameof(filterBy), filterBy, null);
}
}
}
}
This line
return orders.Where(o => o.OrderProducts.AsEnumerable().All(op => op.IsCompleted == true));
With or without .AsEnumerable() for flattening child collection the result is the same. Same error as previous.
Update 10/03 Part 2
Here is an example of an API result if it helps getting a better idea of the hierarchy.
{
"id": "1",
"orderPicture": null,
"saleType": 2,
"clientName": "Toto",
"clientPhone": "000000000",
"fullAddress": "Toto road, 420",
"smsCode": "XYZVDF",
"creationDate": "2020-03-09T15:08:36.157",
"visitDate": "2020-03-16T08:00:00",
"orderProducts": [
{
"productTypeID": 1,
"productName": "A sample product",
"visitDate": "2020-03-16T08:00:00",
"urgenceDate": null,
"description": "7",
"companyEmployeeNames": null,
"isCompleted": false,
"orderFiles": []
}
]
},
My domain class:
public class Address
{
[Key]
public virtual string AddressId { get; set; }
public virtual string Address { get; set; }
}
In my MVC controller I want to check the given Address exist, before I insert.
public ActionResult Create(Address address)
{
if (ModelState.IsValid)
{
if (db.Addresses.Any(a => a.AddressId == address.AddressId)) // how I do it now
{
ModelState.AddModelError(string.Empty, "Address Id already exists!");
}
else
{
db.Addresses.Add(address);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
But there are lot of other domain classes in my project and I want to do the same check again and again.
My question is I want to write a generic method in my Db context class to perform this check. (looks like below or similar)
public bool Exists(object) {
// return true if exist
}
i.e. a method which I can call like this:
db.Exists(address)
Thanks!
You could use generics and do something like the following:
public class YourDbContext : DbContext
{
...
public bool Exists<TEntity>(object id)
where TEntity : class
{
var dbSet = Set<TEntity>();
var entity = dbSet.Find(id);
return entity != null;
}
Which you'd then use like:
db.Exists<Address>(address.AddressId);
Using Find isn't the most efficient way to handle this, but it has the key benefit that you're not required to know what the actual primary key property on the class is, which would greatly complicate this method. For example, Address has AddressId, but Foo might have FooId.
UPDATE
Since ultimately this just uses Find under the hood, you just have to modify the method slightly to be able to take multiple parameters. Find handles composite keys by allowing one more parameters to be passed to it. But bear in mind, the the order matters and must align with the key order you specified when configuring your entity.
public bool Exists<TEntity>(params object[] keys)
where TEntity : class
{
var dbSet = Set<TEntity>();
var entity = dbSet.Find(keys);
return entity != null;
}
For work, we have specific types of records that come in, but each project has its own implementation. The columns and the like are different, but in general the process is the same (records are broken into batches, batches are assigned, batches are completed, batches are returned, batches are sent out, etc.). Many of the columns are common, too, but sometimes there are name changes (BatchId in one vs Id in another. [Column("name")] takes care of this issue).
Currently this is what I have for the implementation of the batch assignment functionality with the common components given in the interface:
public interface IAssignment
{
// properties
...
// methods
T GetAssignmentRecord<T>(int UserId, int BatchId) where T : IAssignment;
List<T> GetAssignmentRecords<T>(int UserId) where T : IAssignment;
}
Now I currently have two projects that have batch assignment. Due to these being done in EntityFramework, Assignment in Namespace1 and Assignment in Namespace2 are completely different things but are bound by certain common components (an ID, an assigned user, checked in, etc.) which drive all of the methods for returning them.
I think my main question is if I'm doing this incorrectly and if there is a better way to achieve this such that I can pipe data into my Controllers and have the controllers look somewhat similar project to project while having as much of the method work being handled automatically (primarily so that a "fix one, fix all" scenario occurs when I need to do updates).
Here's an example of how I'm doing the implementation for namespace1:
public class Assignment
{
...
public T GetAssignmentRecord<T>(int UserId, int BatchId) where T : IAssignment
{
var db = new Database1Context();
return (T) Convert.ChangeType(db.Assignment.Where(c => c.UserId == UserId && c.BatchId == BatchId && c.Assigned).First(), typeof(T));
}
}
In the Controller:
Assignment assignment = new Assignment();
var record = assignment.GetAssignmentRecord<Assignment>(userid, batchid);
// do stuff
The controller code is actually how I'm assuming it would work. I've completed through the Assignment class and now I'm perplexed if I'm doing it the proper way. The reason I feel this may be incorrect is I'm basically saying "The interface is looking for a generic, I'm getting a strong typed object from the database using entity framework, I'm casting it to a generic, and when I'm making the request, I'm asking for the same strong typed object that I converted to generic initially."
Is there a better way of doing this? Or a completely different direction I should be going?
Providing I understood correctly what your goal is, I'd do it e.g. this way...
interface IAssignment
{
}
interface IRepo<out T> where T : IAssignment
{
T GetAssignmentRecord(int UserId, int BatchId);
IEnumerable<T> GetAssignmentRecords(int UserId);
}
class AssignmentRecord : IAssignment
{
}
class AssignmentWeb : IAssignment
{
}
class RepoDb : IRepo<AssignmentRecord>
{
public AssignmentRecord GetAssignmentRecord(int UserId, int BatchId)
{
//using(var db = new MyDbContext())
//{
// return db.Assignment.Where(c => c.UserId == UserId && c.BatchId == BatchId && c.Assigned).First();
//}
return new AssignmentRecord();
}
public IEnumerable<AssignmentRecord> GetAssignmentRecords(int UserId)
{
//using(var db = new MyDbContext())
//{
// return db.Assignment.Where(c => c.UserId == UserId && c.BatchId == BatchId && c.Assigned);
//}
return new List<AssignmentRecord>
{
new AssignmentRecord(),
new AssignmentRecord(),
new AssignmentRecord(),
new AssignmentRecord(),
new AssignmentRecord(),
new AssignmentRecord(),
new AssignmentRecord(),
new AssignmentRecord(),
};
}
}
class RepoWeb : IRepo<AssignmentWeb>
{
public AssignmentWeb GetAssignmentRecord(int UserId, int BatchId)
{
// fetch it from some web service...
return new AssignmentWeb();
}
public IEnumerable<AssignmentWeb> GetAssignmentRecords(int UserId)
{
//using(var db = new MyDbContext())
//{
// return db.Assignment.Where(c => c.UserId == UserId && c.BatchId == BatchId && c.Assigned);
//}
return new List<AssignmentWeb>
{
new AssignmentWeb(),
new AssignmentWeb(),
new AssignmentWeb(),
};
}
}
class MYController
{
public IRepo<IAssignment> Repository { get; set; } // you can inject this e.g. DI
public IAssignment GetAssignment(int userid, int batchid)
{
return Repository.GetAssignmentRecord(userid, batchid);
}
public IEnumerable<IAssignment> GetAllAssignments(int userid)
{
return Repository.GetAssignmentRecords(userid);
}
}
class ProgramAssignment
{
static void Main(string[] args)
{
try
{
var controller = new MYController();
controller.Repository = new RepoDb();
IAssignment assignment = controller.GetAssignment(0, 0);
IEnumerable<IAssignment> all = controller.GetAllAssignments(0);
controller.Repository = new RepoWeb();
assignment = controller.GetAssignment(0, 0);
all = controller.GetAllAssignments(0);
}
catch
{
Console.WriteLine("");
}
}
}
As to why the out - here is some more in my other post...
How to make generic class that contains a Set of only its own type or subtypes as Children?
Assuming that the 2 Assignment has different properties (maybe some additional), but some of the property is same, and they are from different database, there are many ways to doing it. But "the best" (for me) is by doing dependency injection.
Your activities (methods) in Assignment class, should be moved to a separated "service" class. This increases the modularity of Assignment, as it only became a POCO.
For data access, create a separated class (repository) to retrieve/insert/update/delete your data. Example will be like:
public AssignmentRepository: IAssignmentRepository{
public Assignment GetAssignmentRecord(int userId, int batchId){
}
}
public BatchAssignmentRepository: IAssignmentRepository{
public Assignment GetAssignmentRecord(int userId, int batchId){
}
}
If you ask why there are 2 repository instead of 1, will it make the code redundant? Yes it is, but you also must consider it will increase the modularity. If you change something in BatchAssignment (maybe change the column name, add additional column, etc) then you do not need to apply the same in Assignment, and avoiding you of "if batchAssignment else" logic inside.
The use from the caller will be like this:
IAssignmentService service = new AssignmentService();
IAssignmentRepository repository = new AssignmentRepository();
Assignment a = repository.GetAssignmentRecord(userId, batchId);
service.DoSomething(a);
Think about an adapter layer. That layer should transform the incoming data to a common structure/class and then can be handled consistently, generics notwithstanding. Of course it also re-transforms on the "outbound" side to that expected by the particular databases. This assumes that no datasource has data that is undefined in the others, or that you can define valid default values for said missing data.
I imagine you need different adapters for the different projects. Perhaps this is a job for dependency injection. Basically at runtime you fetch the particular code (adapter class) needed.
Introduction to Unity.
I am hoping to create a base Entity Class that includes a validation rule that checks if a field called "Title" is unique (which of course requires a db scan). I want the inherited models to run the validation rule in the repo (or service) layer and send a ValidationResult to the (MVC) client-layer.
The problem is one of inheritance.
public interface IUniqueTitle
{
int Id { get; set; }
string Title { get; set; }
// This is a "multi-client, one database" solution.
// Data is isolated using SiteId
int SiteId { get; set; }
}
// Models such as "MemberClub" and "Assessment" will inherit from this
public class EntityUniqueTitle : IUniqueTitle
{
public int Id { get; set; }
public int SiteId { get; set; }
public string Title { get; set; }
}
// This class will be used in production
public class MemberClub : EntityUniqueTitle
{
}
I wrote an extension method that to check to see if the Title field is unique based on the SiteId
public static bool IsUniqueTitle<T>(this IQueryable<T> items, T currentEntity) where T : IUniqueTitle
{
return items.Where(
item => item.Id != currentEntity.Id // INCASE UPDATING OBJECT
& item.SiteId == currentEntity.SiteId
& item.Title == currentEntity.Title)
.Count() == 0;
}
Here is where I get stuck. Where should I put the validation?
I can put in the Repo but can't figure out how to fire the ValidationResult upon Save
public class RepoUniqueTitle<T> : IRepoUniqueTitle<T> where T : EntityUniqueTitle, new()
{
protected readonly DbContext c;
public Repo(IDbContextFactory f) { c = f.GetContext(); }
public void Insert(T o)
{
if (!c.Set<T>().IsUniqueTitle(o))
{
// ***********************
// PROBLEM HERE, HOW DO I STOP AND SEND A VALIDATIONRESULT TO THE CLIENT?
// IF POSSIBLE, AUOTMATIC WHEN MODEL.ISVALID IS CALLED
// code from base repo class for reference
// if (o is IUniqueTitleForSite)
// IoC.Resolve<IRepoUniqueTitle<T>>().Validate(o);
}
else
c.Set<T>().Add(o);
}
}
I am hoping there is a validation solution that:
Models can inherit from a base Entity
Can make db calls to the inherited entity's collection
Works with ValidationResult so it can be cleanly integrated into MVC Tier
Called during Model.isValid if possible
Note: I am using ProDinner as a basis for an "n-tier code-first EF mvc/wf" solution.
Sorry, a lot of this is new to me. Any help you can provide would be greatly appreciated!
If you have separate business logic layer you should place the validation to that layer. Otherwise why to have that layer if you don't use it to execute business rules?
Anyway unique check is tricky because there is a delay between your query and actual saving of data and another thread can insert the item with the same title during that delay. You should place unique index on Title and SiteId to enforce uniqueness in the database. In such case former problem will result in the exception which you must handle somehow but it is probably better then data duplicity.
I have two objects... and if I compile a program with either one, it works fine, but when they both exist in the same program, I get the exception...
"Entities in 'ObjectContext.UnitSet' participate in the 'Sheet_Statistics' relationship. 0 related 'Sheet' were found. 1 'Sheet' is expected."
class Unit
{
public int Id;
public string Name;
}
class Template
{
public int Id;
public virtual ICollection<Unit> Units
}
class Sheet
{
public int Id;
public virtual ICollection<Unit> Units
}
Then their configurations..
TemplateConfiguration : EntityConfiguration
// ....
//// map the collection entity
HasMany(k => k.Units).WithRequired()
.Map("template.units",
(template, unit) => new
{
Template = template.Id,
Unit = unit.Id
});
SheetConfiguration : EntityConfiguration
// ....
//// map the collection entity
HasMany(k => k.Units).WithRequired()
.Map("sheet.units",
(sheet, unit) => new
{
Sheet = sheet.Id,
Unit = unit.Id
});
UnitConfiguration : EntityConfiguration<Unit>
//
// Initialize the Primary Key
HasKey(k => k.Id);
// Initialize that the Key Increments as an Identity
Property(k => k.Id).IsIdentity();
var templates = new List<Template>
{
new Template
{
Name = // ..,
Units = new List<Unit>
{
new Unit
{
// ...
}
}
}
};
templates.ForEach(x =>
{
context.Templates.Add(x);
});
context.SaveChanges(); // <-- Exception Happens Here, I never even get to try to add Sheets.
I'm taking a stab at this because without seeing all your code, I can't solve much more of it. I think your problem is that you're creating Units but not setting some sort of Sheet property (you need to provide all your entity/config code). You need to create the Sheet and Unit both before you can save the Unit or Sheet since they have a required reference (hence the error you're getting). If you provide more code I'll be able to refine my answer better.