How can I keep custom data annotations/fluent api when I scaffold -force in ef core 2.0? - asp.net-core-2.0

I'm using EF core 2 with existing database.
Used Scaffold-DbContext command to reverse engineer classes. This worked fine.
Even Fluent API has pulled through correct database field constraints etc. e.g.
. . . .
modelBuilder.Entity<FsReport>(entity =>
{
entity.Property(e => e.Bcclist)
.IsUnicode(false)
.HasDefaultValueSql("('')");
entity.Property(e => e.Cclist)
.IsUnicode(false)
.HasDefaultValueSql("('')");
. . . .
Problem is for 1 field I need to add a custom data annotation. If I add this, then as soon as I rerun Scaffold-DbContext -force command (e.g. if database schema change) then the annotation will be overwritten and removed.
Is there anything I can do to avoid this? I tried putting it in a different partial class of the same name but got a 'The type already contains a definition for.... error'
Many thanks for any help.

I have a similiar situation where I wanted to re-generate the scaffold based on "outside-my-control" DB changes and maintain my custom DbContext edits. I solved this by sub-classing the generated DbContext (referring to it here as ScaffoldDbContext). In the subclass (called ScaffoldNavigationDbContext) I call the base.OnModelCreating before making my custom edits on the modelBuilder.
The scaffolding code generates the entity classes as partial so I take advantage and introduce those navigation properties in my classes....separate from the scaffold generated classes. For example, below is my code that will 'survive' a new scaffold generation. There may be caveat's to this but I'm unaware of them and for my simple use-case, it works.
public partial class ScaffoldNavigationDbContext : ScaffoldDbContext {
public ScaffoldNavigationDbContext (DbContextOptions options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<CertMaster>(entity => {
entity.HasMany(x => x.CertTrans).WithOne(x => x.CertMaster).HasPrincipalKey(x => x.CertId);
});
}
}
public partial class CertMaster {
public List<CertTrans> CertTrans{ get; set; }
}
public partial class CertTrans {
public CertMaster CertMaster { get; set; }
}

No. If you insist on sticking with running Scaffold-DbContext (Database first) you will need to add your change back in afterwards. The best way to go forward is to read up on and implement Entity Framework Core Migrations (Code first).

Related

Entity Framework Core 5 tries to insert value for computed column

Using Entity Framework 5 and EF Core Power Tools (whith the setting to generate ef 5 code enabled) we get something like the following code generated for each computed column:
...HasComputedColumnSql("([LastName]+isnull(' '+[FirstName],''))", true);
which is perfectly right.
Unfortunately, trying to create a new entity that has computed columns and trying to save we get the following exception from ef core 5:
The column '...' cannot be modified because it is either a computed column or is the result of a UNION operator
When manualy appending the ValueGeneratedOnAddOrUpdate after the HasComputedColumnSql like so:
...HasComputedColumnSql("([LastName]+isnull(' '+[FirstName],''))", true).ValueGeneratedOnAddOrUpdate();
everything works fine.
According to the docs this should be automatically be called by HasComputedColumnSql.
What can be done to overcome this issue ?
We are using Microsoft.EntityFrameworkCore v5.0.5 package and also we are using an Azure Managed Sql Server Instance
The OP already pointed out the solution: in EF core 5, when scaffolding is used, you have to manually add ValueGeneratedOnAddOrUpdate(). This problem has been fixed in EF core 6.
The patch has been mentioned in earlier answers.
This answer elaborates on how to remove manual code from generated code.
Let's say the scaffolded file GenDbContext.cs contains:
modelBuilder.Entity<MyEntity>(entity =>
{
entity.Property(e => e.ComputedName).HasMaxLength(100).IsUnicode(false)
.HasComputedColumnSql("([LastName]+isnull(' '+[FirstName],''))", true);
});
There are two options: partial class or inheritance.
A) Partial class in GenDbContext.Extension.cs - requires EF core 3.1+
public partial class GenDbContext : DbContext
{
partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>(entity =>
entity.Property(e => e.ComputedName).ValueGeneratedOnAddOrUpdate()
);
}
}
B) Subclass in InheritedDbContext.cs.
public class InheritedDbContext : GenDbContext
{
public InheritedDbContext(DbContextOptions<GenDbContext> options) : base(options) {}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyEntity>(entity =>
entity.Property(e => e.ComputedName).ValueGeneratedOnAddOrUpdate()
);
}
}
Then in Startup.cs:
// The first line is still necessary as it declares DbContextOptions<GenDbContext>.
services.AddDbContext<GenDbContext>(...options...);
services.AddDbContext<InheritedDbContext>(...same options...);
There is no need to repeat the rest of the column settings such as HasMaxLength, IsUnicode and HasComputedColumnSql.

Intercepting Fluent Validation

We are using fluentvalidation (with service stack) to validate our request DTO's. We have recently extended our framework to accept "PATCH" requests, which means we now have a requirement to apply validation ONLY when the patch contained the field being validated.
We have done this using an extension method such as this:
RuleFor(dto => dto.FirstName).Length(1,30)).WhenFieldInPatch((MyRequest dto)=>dto.FirstName);
RuleFor(dto => dto.MiddleName).Length(1,30)).WhenFieldInPatch((MyRequest dto)=>dto.MiddleName);
RuleFor(dto => dto.LastName).Length(1,30)).WhenFieldInPatch((MyRequest dto)=>dto.LastName);
This means we can run the same validation for a POST/PUT or a PATCH.
I have been looking for a way of hooking in to the fluent validation framework in such as way that we do not need to duplicate the .WhenFieldInPatch() rule on EVERY line in our validations, but have not yet found a nice way to do this.
I have tried the following:
Creating a helper method (in a in a base class) to intercept the initial "RuleFor" which adds the .When() clause up front, but the this does not work as fluent validation requires the .When() to be last
Intercepting the calls in PreValidation, but I can only intercept based on the whole class, and not on a rule by rule basis
Adding an extension method to apply to the end of every rule (as per example), but I cannot access the initial expression in order to check whether the field should be mapped - so I need to pass it in again.
Am I missing something, or am I attempting the impossible?
Thanks
When I need to share Fluent Validation Logic I'd use extension methods, here's an example of shared Extension methods for TechStacks, e.g:
public static class ValidatorUtils
{
public static bool IsValidUrl(string arg) => Uri.TryCreate(arg, UriKind.Absolute, out _);
public static string InvalidUrlMessage = "Invalid URL";
public static IRuleBuilderOptions<T, string> OptionalUrl<T>(
this IRuleBuilderInitial<T, string> propertyRule)
{
return propertyRule
.Length(0, UrlMaxLength)
.Must(IsValidUrl)
.When(x => !string.IsNullOrEmpty(x as string))
.WithMessage(InvalidUrlMessage);
}
}
And some examples where they're shared:
public class CreatePostValidator : AbstractValidator<CreatePost>
{
public CreatePostValidator()
{
RuleSet(ApplyTo.Post, () =>
{
RuleFor(x => x.Url).OptionalUrl();
});
}
}
public class UpdatePostValidator : AbstractValidator<UpdatePost>
{
public UpdatePostValidator()
{
RuleSet(ApplyTo.Put, () =>
{
RuleFor(x => x.Url).OptionalUrl();
});
}
}

Is there a way to ignore some entity properties when calling EdmxWriter.WriteEdmx

I am specifically using breezejs and the server code for breeze js converts the dbcontext into a form which is useable on the clientside using EdmxWriter.WriteEdmx. There are many properties which I have added JsonIgnore attributes to so that they don't get passed to the client side. However, the metadata that is generated (and passed to the clientside) from EdmxWriter.WriteEdmx still has those properties. Is there any additional attribute that I can add to those properties that I want ignored so that they are ignored by EdmxWriter.WriteEdmx? Or, would I need to make a separate method so as not to have any other unintended side effects.
You can sub-class your DbContext with a more restrictive variant that you use solely for metadata generation. You can continue to use your base context for persistence purposes.
The DocCode sample illustrates this technique with its NorthwindMetadataContext which hides the UserSessionId property from the metadata.
It's just a few extra lines of code that do the trick.
public class NorthwindMetadataContext : NorthwindContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Hide from clients
modelBuilder.Entity<Customer>().Ignore(t => t.CustomerID_OLD);
// Ignore UserSessionId in metadata (but keep it in base DbContext)
modelBuilder.Entity<Customer>().Ignore(t => t.UserSessionId);
modelBuilder.Entity<Employee>().Ignore(t => t.UserSessionId);
modelBuilder.Entity<Order>().Ignore(t => t.UserSessionId);
// ... more of the same ...
}
}
The Web API controller delegates to the NorthwindRepository where you'll see that the Metadata property gets metadata from the NorthwindMetadataContext while the other repository members reference an EFContextProvider for the full NorthwindContext.
public class NorthwindRepository
{
public NorthwindRepository()
{
_contextProvider = new EFContextProvider<NorthwindContext>();
}
public string Metadata
{
get
{
// Returns metadata from a dedicated DbContext that is different from
// the DbContext used for other operations
// See NorthwindMetadataContext for more about the scenario behind this.
var metaContextProvider = new EFContextProvider<NorthwindMetadataContext>();
return metaContextProvider.Metadata();
}
}
public SaveResult SaveChanges(JObject saveBundle)
{
PrepareSaveGuard();
return _contextProvider.SaveChanges(saveBundle);
}
public IQueryable<Category> Categories {
get { return Context.Categories; }
}
// ... more members ...
}
Pretty clever, eh?
Just remember that the UserSessionId is still on the server-side class model and could be set by a rogue client's saveChanges requests. DocCode guards against that risk in its SaveChanges validation processing.
You can sub-class your DbContext with a more restrictive variant that you use solely for metadata generation. You can continue to use your base context for persistence purposes.
The DocCode sample illustrates this technique with its NorthwindMetadataContext which hides the UserSessionId property from the metadata.
It's just a few extra lines of code that do the trick.
The Web API controller delegates to the NorthwindRepository where you'll see that the Metadata property gets metadata from the NorthwindMetadataContext while the other repository members reference an EFContextProvider for the full NorthwindContext.
Pretty clever, eh?
If you use the [NotMapped] attribute on a property, then it should be ignored by the EDMX process.

Breeze & EFContextProvider - How to properly return $type when using expand()?

I am using Breeze with much success in my SPA, but seem to be stuck when trying to return parent->child data in a single query by using expand().
When doing a single table query, the $type in the JSON return is correct:
$type: MySPA.Models.Challenge, MySPA
However if I use expand() in my query I get the relational data, but the $type is this:
System.Collections.Generic.Dictionary 2[[System.String, mscorlib],[System.Object, mscorlib]]
Because of the $type is not the proper table + namespace, the client side code can't tell that this is an entity and exposes it as JSON and not a Breeze object (with observables, entityAspect, etc.).
At first I was using my own ContextProvider so that I could override the Before/After saving methods. When I had these problems, I reverted back to the stock EFContextProvider<>.
I am using EF5 in a database first mode.
Here's my controller code:
[BreezeController]
public class DataController : ApiController
{
// readonly ModelProvider _contextProvider = new ModelProvider();
readonly EFContextProvider<TestEntities> _contextProvider = new EFContextProvider<TestEntities>();
[HttpGet]
public string Metadata()
{
return _contextProvider.Metadata();
}
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
[HttpGet]
public IQueryable<Challenge> Challenges()
{
return _contextProvider.Context.Challenges;
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
public IQueryable<ChallengeNote> ChallengeNotes()
{
return _contextProvider.Context.ChallengeNotes;
}
}
Here's my BreezeWebApiConfig.cs
public static void RegisterBreezePreStart()
{
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "breeze/{controller}/{action}"
);
}
Is there a configuration setting that I am missing?
Did you try "expanding" on server side? Is it needed to do expand on client side? I tried to do expand before but failed for me as well, did some research and decided I'd rather place it on server:
[HttpGet]
public IQueryable<Challenge> ChallengesWithNotes()
{
return _contextProvider.Context.Challenges.Include("ChallengeNotes");
}
This should be parsed as expected. On client side you would query for "ChallengeNotes" instead of "Challenges" and you wouldn't need to write expand part.
I strongly suspect that the problem is due to your use of the [Queryable] attribute.
You must use the [BreezeQueryable] attribute instead!
See the documentation on limiting queries.
We are aware that Web API's QueryableAttribute has been deprecated in favor of EnableQueryAttribute in Web API v.1.5. Please stick with BreezeQueryable until we've had a chance to write a corresponding derived attribute for EnableQuery. Check with the documentation for the status of this development.

Is it possible to create an orchard autoroute using contents of a custom type property?

I have an Orchard cms module with some additional Content types set up and have added an AutoRoute component via code.
Everything works perfectly, however I am not happy with the default permalink pattern.
What I am trying to do is add a custom pattern and use one of the public properties in my content type. In my case the custom type has a public property called ClubName and I would like that to be used (It makes more sense from a routing perspective).
The Orchard part class name is called TrackPart.
I have tried {Content.TrackPart.ClubName}, {Content.Track.ClubName}, {ContentItem.TrackPart.ClubName},{Content.TrackPart.ClubName} and various other variations but nothing seems to be working.
I am really new to Orchard so there is a high chance I am missing something simple.
Any help would be greatly appreciated.
In response to feedback from #Bertrand-le-roy I created my own token by copying an example token. I can now get see the token in the drop down menu and select it. However the route pattern is still not working.
I can only assume that I have misunderstood the Evaluate() function's context.For usage. It looks like I am not getting the data I need
Here is what I have so far.
public class TrackPartTokens : ITokenProvider {
private readonly IContentManager _contentManager;
public TrackPartTokens(IContentManager contentManager) {
_contentManager = contentManager;
}
public Localizer T { get; set; }
public void Describe(dynamic context) {
context.For("Track", T("Track"), T("Tokens for Track"))
.Token("ClubName", T("ClubName"), T("The name of the club."))
;
}
public void Evaluate(dynamic context) {
context.For<TrackPart>("Track")
.Token("ClubName", (Func<TrackPart, object>)(field => field.ClubName))
.Chain("ClubName", "ClubName", (Func<TrackPart, object>)(field =>field.ClubName))
;
}</code>
The above code was based on the DateTimeField token inside the Orchard.Fields module.
context.For("DateTimeField")
.Token("Date", (Func)(field => field.DateTime))
.Chain("Date", "Date", (Func)(field => field.DateTime));
I had the same issue.
After some troubleshooting I managed to get the autoroute working by changing my implementaion to the following (adapted to your example, note that your setup might require some changes to the linq-function):
In your tokens-class:
First add a using System.Linq statement.
Then change your Evaluate implementation to the following:
context.For<IContent>("Content")
.Token("ClubName", (Func<IContent>, object>)(content =>
content.ContentItem.Parts.OfType<TrackPart>().First().ClubName));
Make sure your AutoroutePart settings in Migrations.cs uses the Content-prefix. Like:
.WithPart("AutoroutePart", partBuilder =>
partBuilder
.WithSetting("AutorouteSettings.AllowCustomPattern", "true")
.WithSetting("AutorouteSettings.AutomaticAdjustmentOnEdit", "false")
.WithSetting("AutorouteSettings.PatternDefinitions",
#"[{Name:'Track', Pattern:'{Content.ClubName}',
Description:'Your description'}]")
.WithSetting("AutorouteSettings.DefaultPatternIndex", "0"))
There seems to be some problems with the TokenManager-class in Orchard source that only allows the target-parameter to equal "Content" in order for the call: _data.TryGetValue(target, out value) to work (TokenManager.cs, line 67). I have tried a number of different setups but the _data-dictionary always only contain the "Content" key.
You'll have to make your own token. It's really easy. Copy a working example.

Resources