0

I find myself repeating many many times the same attributes I apply to properties of classes, that are used to represent both entity framework entities AND objects that will be edited in Blazor client forms (but it could be anything else, I think).

Trying to save me some typing (and probably also errors) I googled around to find some technique to minimize the code I write.

I ended up finding this solution:
Combining multiple Attributes to a single Attribute - Merge Attributes

That's really helpful to me for the editing phase. When I use such approach on properties that will be edited in Blazor forms, I get all the attribute-related errors if it's the case, and all works.

Unfortunately, apparently this approach is not taken into account by the Entity Framework migration generator, so, unless I made some big error, they use two different (sub)technologies to generate constraints for the given property.
(Example: I apply to a string property 'Required', 'MinLength' and 'MaxLength' attributes through my custom metadata attribute, as per the linked article, and the field is generated in the migration without any of these constraints, although they are applied in a form)

Indeed, I can use custom conventions, to obtain the same result on the side of entity framework (something explained for example in https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/conventions/custom), but this would mean that I would have to apply two different attributes/approaches to the same property, each time (also risking to mis-align things...).

It looks so strange, to me... Any ideas, from anyone, please?

*** Update

After the motivated answer by Chris, that I really thank, I can add a more realistic example, from the (test) code I'm writing.

[Required]
[MinLength(Constants.ShortStringFieldMinLength, ErrorMessage = Constants.ShortStringFieldMinLengthErrorMessage)]
[MaxLength(Constants.ShortStringFieldMaxLength, ErrorMessage = Constants.ShortStringFieldMaxLengthErrorMessage))]
[RegularExpression(Constants.ShortNameRegEx, ErrorMessage = Constants.ShortNameRegExError)]
[DisplayName("Name of something")]
public string Name { get; set; } = string.Empty;

[Required]
[MinLength(Constants.LongStringFieldMinLength, ErrorMessage = Constants.LongStringFieldMinLengthErrorMessage)]
[MaxLength(Constants.LongStringFieldMaxLength, ErrorMessage = Constants.LongStringFieldMaxLengthErrorMessage))]
[RegularExpression(Constants.LongStringFieldRegEx, ErrorMessage = Constants.LongStringFieldRegExErrorMessage)]
[DisplayName("Description of something")]
public string Description{ get; set; } = string.Empty;

A long entity object described in this way is really less than readable, other than repeating many times the same declaration for many of the properties (long string fields and short string fields, fundamentally... maybe also 'medium', with time?).

It would much better if I could write:

[ShortStringField("Name of something")]
public string Name {get; set; } = string.Empty;

[LongStringField("Description of something")]
public string Description {get; set; } = string.Empty;

Am I overengineering? :-) Maybe! I just find much more pleasant the second syntax, if I can get to it, someway.

I obviously know that some of the attributes will be applied only to editing (DisplayName, for example), but I'm ok with that, of course.

Given that there are two solutions for the two different parts of the problem, I was just wondering if there's an architectural way to reach both services together: maybe the resulting code can be a little bit complex, but it would be just a one shot, obviously, and the reuse very easy and flexible.

Andrea
  • 555
  • 5
  • 28
  • Given that your Min and Max length constraints are based on constants, this is what conventions is for, you just need to create a convention for the `ShortStringField` and the `LongStringField` Work smarter, not harder ;) – Chris Schaller Oct 13 '22 at 02:56
  • That was exactly my point: as I said, if I create a convention, the attributes are NOT applied to the forms... :-) sometimes you have to work harder, to be smarter! :-) – Andrea Oct 13 '22 at 11:01
  • So you do both, you already have the attributes working in blazor, now you need to make a single convention to get the same rules and logic to apply in EF, you're like 95% done – Chris Schaller Oct 13 '22 at 11:35
  • :-) After a long trip, we are at the starting point. I started saying that there are two different ways, each one solving one part of the problem: I can use them both, but it's not the best solution, risking to misalign definitions, and whatever. Anyway, this conversation helped me focusing better on the problem. Thank you a lot for your patience and your time. – Andrea Oct 14 '22 at 12:05
  • We often have to be pragmatic about this. Follow the path of least resistance now that you are half way there. If you don't want conflicting sets of configuration, then do it the verbose way, by using the explicit attributes. When you start to get too clever you are taking a lot of responsibility to get it all right. In the long term I find the _merged attributes_ gets in the way of delivering a polished product. – Chris Schaller Oct 14 '22 at 15:16
  • I will try to make good use of these words! :-) – Andrea Oct 17 '22 at 12:34

1 Answers1

0

The EF Code-First conventions rely on type matching of the attributes to set the fluent configuration. You would either need to implement your own custom convention to interpret your Merged attributes and run out the same rules, not hard and you only do it once, so it's an option for you.

Or go back to applying the attributes as the C# gods had intended. The concept of conventions is pretty much trying to solve the same thing as your merged attributes, but in an inverted way.

The convention pattern is that you have broad rules or statements that cover the general majority of your code base, then we can use attributes to trigger conditional rules or to override the general rules.

You might have found it easier to implement conventions for Blazor, I certainly did, than going down the Merged Attribute route, which I tried many years ago with WinForms.

If you have a lot of logic already in-place and working for Blazor, the follow some of the sources from this answer to assist you. Conventions in EF are pretty straight forward.

The problem with the Merged Attributes is that as you have discovered, not all frameworks and runtimes will work as their authors have expected, but also the developers in your team will be coding in a non-standard way. This approach can easily generate negative work.

What you might be able to do is a hybrid, where the standard DataAnnotations are used in your data models, and the Merged Attribute could implement some logic to return the missing attributes, based on your custom conventional rules.


Your example is a bit hypothetical to go into examples. My personal Blazor runtimes have a OData service layer in between the client and the server, so I generate out the client proxies and use conventions to include any relevant attributes on the models, but I wouldn't say there are enough common attributes in any of my projects that warrant the use of Merging them into common groups... I wonder if you are over-engineering or if this is an X-Y thing where you are using attributes to solve something else that we already have a standard solution for... I am genuinely wondering how I get by without having to do this myself, what am I missing out on?

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81