GithubHelp home page GithubHelp logo

avanade / beef Goto Github PK

View Code? Open in Web Editor NEW
132.0 18.0 38.0 6.65 MB

The Business Entity Execution Framework (Beef) framework, and the underlying code generation, has been primarily created to support the industrialization of API development.

License: MIT License

C# 79.50% PowerShell 0.56% TSQL 0.57% Handlebars 19.37%
beef apis entity-framework cosmos architecture code-generation event-driven framework webapi dotnetcore

beef's Introduction


Logo


Introduction

Beef framework has been primarily created to support the industralisation of API development.

A means to have software developers focus directly on the accelerated delivery of business value; with consistently higher quality outcomes at an overall lower cost.

The key industralisation goals are:

  1. Value – focus on business value, not on boilerplate
  2. Acceleration – improve velocity; reduce costs and time to market
  3. Simplicity – increase effective usage and minimize learning
  4. Standardized – increase knowledgeable resource pool
  5. Consistency – improve overall quality and maintainability
  6. Flexibility – enable innovation and evolution easily over time
  7. Confidence – reduced delivery timeframes and risk

Demonstration

The following video (includes sound) provides a high-level demonstration of Beef and its capabilities.

beef-demo.mp4

Version 5

NOTE: A new major version of Beef, verison 5, was released on 26-Jan-2023. The samples have been updated accordingly, as has the related documentation. As such, development on the previous version (v4.x) will for the most part halt; only significant issues will now be addressed (for a period of time). The new version is a major refactoring (improvement and simplification) with respect to the underlying runtime primarily, and although effort was made to minimize impacts on upgrading from v4, this was unfortunately unavoidable. Guidance is provided to assist with upgrading from v4.2.x to v5.x where applicable.


Composition

Beef is ostensibly the code-generation engine, and solution orchestration, that ultimately takes dependencies on the following capabilities to enable the end-to-functionality and testing thereof in a standardized (albiet somewhat opinionated) manner:

  • CoreEx - provides the core runtime capabilties (extends .NET core);
  • UnitTestEx - provides extended unit and intra-domain integration testing;
  • DbEx - provides extended database management capabilties;
  • OnRamp - provides the underlying code-generation engine functionality.

Prior to version 5.x, Beef was all encompassing. These capabilities have been extracted, simplified and refactored to be first class frameworks in their own right, and made into the repos listed above. This allows them to be used and maintained independently to Beef; therefore, offering greater opportunities for reuse versus all-or-nothing.


Rapid enterprise-grade API development

As a result of the Beef Architecture, supporting Framework and included Code Generation capabilities, enterprise-grade APIs can be developed in a matter of hours, not days, in a standardized and consistent manner.

The APIs created will have the following capabilities out-of-the-box with limited developer effort, so the developer can focus on the key business value:

  • Rich Entity (DTO) functionality including INotifyPropertyChanged, IEditableObject, IEquatable, ICopyFrom, ICleanUp, IPrimaryKey, etc.
  • Rich Reference data capabilities, including caching, optimized serialisation, and enriched API endpoints.
  • Rich Validation capability to simplify and ensure data integrity and consistency.
  • CRUD (Create, Read, Update and Delete) for Database (Stored procedures and Entity Framework), Cosmos DB and HttpAgent in a standardized manner.
  • Paging (skip and top) and resulting total count, that flows from API through to the underlying data source in a consistent and seamless manner.
  • ETag (concurrency) and If-Match/If-None-Match handling.
  • JSON response field filtering (include/exclude) to minimize resulting payload size (e.g. $fields=firstname,lastname)
  • HTTP Patch support, where required, in a simplified and consistent manner.
  • An end-to-end intra-domain integration testing approach enables effective tests to be built easily and quickly.
  • Event publishing (and subscribing) to enable an event-driven architecture.
  • Runtime capabilities can be added, where applicable, using the out-of-the-box .NET framework Dependency Injection (DI) capabilites.
  • An approach and tooling to automate and manage database using DbEx set up, configuration, and deployment.

To implement these included capabilities would literally take months/years to build and test; these are available for developers to use immediately, and contribute back if so inclined. The capabilities and implementations have been influenced by Microsoft's best practices for cloud applications; specifically:

To get started a .NET Core template capability is provided to enable you to get a solution up and running in minutes.


Architecture

Beef has been developed to encourage the standardisation and industrialisation of the tiering and layering within the microservices (APIs) of an Application Architecture.


API-enabled domain-based channel-agnostic architecture

The conceptual architecture is as follows; with Beef being targeted specifically at implementation of the API tier.

Domains

The key concepts are as follows:

  • Channel-agnostic - the APIs are based around the key entities and the operations that can be performed on them:

    • APIs represent the key trust boundary; as such, they make no assumptions on the consumer. The APIs will always validate the request data, and house the application’s functional business and orchestration rules.
    • APIs should not be developed to service a specific user interface interaction; as the APIs are agnostic to the consumer. The consumer has the responsibility of coordinating across API calls.
  • Domain-based – the APIs are based around, and encapsulate, the capabilities for a functional domain:


Microservices

An architectural pattern for creating domain-based APIs:

  • Is a software architecture style in which complex applications are composed of small, independent processes communicating with each other using language-agnostic APIs.
  • These services are small, highly decoupled and focus on doing a small task, facilitating a modular approach to system-building.
  • Implementation independence:
    • Loose coupling – should have its own persistence repository; data is duplicated (synchronized), not shared; eventual consistency; no distributed transactions.
    • Polyglot persistence / programming – use the best persistence repository to support the storage requirements; use a mix of programming languages (fit-for-purpose). Note: Beef provides a C# / .NET implementation approach as one option.
    • Eventual consistency - for the most part, eventual consistency is good enough; real-time distributed transactional integrity is rarely required (although generally desired). An asynchronous messaging system, such as Queues or a Service Bus, can be leveraged to orchestrate cross domain data (eventual) consistency.

“Micro” doesn’t imply number of lines of code; but a bounded concept / business capability within your Domain. - http://herdingcode.com


Tiering and layering

The architecture supports a domain-based channel-agnostic microservices approach. The API service endpoints represent a light-weight facade for the Business (domain logic) tier, that is ultimately responsible for the fulfillment of the request.

The following represents the prescribed tiering and layering of the architecture:

Layers

Given this architecture, the .NET Solution you create using Beef should adhere to the prescribed solution structure.

Each of the key layers / components above are further detailed (Xxx denotes the entity name); further documentation for each is available via the provided links:


Event-driven

To support the goals of an Event-driven Architecture Beef enables the key capabilities; the publishing (Producer) and subscribing (Consumer) of events (messages) to and from an event-stream (or equivalent).

Layers

  • Producer / Publisher - the publishing of events is integrated into the API processing pipeline; this is enabled within either the Data (where leveraging the transactional outbox pattern) or Service orchestration layers to ensure consistency of approach. Beef is largely agnostic to the underlying event/messaging infrastructure (event-stream) and must be implemented by the developer (unless provided, see Azure ServiceBus).

  • Consumer / Subscriber - a event subscriber is then implemented to listen to events from the underlying event/messaging infrastructure (event-stream) and perform the related action. The event subscriber is encouraged to re-use the underlying logic by hosting the Beef capabilities to implement. The Domain logic layer can be re-leveraged to perform the underlying business logic on the receipt of an event (within the context of a subscribing domain).

Additionally, Beef has capabilities to support (and generate) the Transactional Outbox Pattern where there is a requirement for events to be sent reliably (with no message loss); i.e. to guarantee at-least-once sent semantics within the context of the underlying data update (currently only supported for Database repository).


Framework

A comprehensive framework has been created to support the defined architecture, to encapsulate and standardize capabilities, to achieve the desired code-generation outcomes and improve the overall developer experience.

Standardized approach, ensures consistency of implementation:

  • Reduction in development effort.
  • Higher quality of output; reduced defects.
  • Greater confidence in adherence to architectural vision; minimized deviation.
  • Generation and alike enables the solution to evolve more quickly and effectively over time.

A key accelerator for Beef is achieved using a flexible code generation approach leveraging OnRamp.


Packages

The key tooling capabilities for Beef are enabled by the following NuGet packages (version 5+). The included change log details all key changes per published version.

Assembly Description NuGet
Beef.CodeGen.Core Code generation console tool. NuGet version
Beef.Database.Core Database and data management console tool. NuGet version
Beef.Database.MySql MySQL database and data management console tool. NuGet version
Beef.Database.Postgres PostgreSQL database and data management console tool. NuGet version
Beef.Database.SqlServer SQL Server database and data management console tool. NuGet version
Beef.Template.Solution Solution and projects template. NuGet version

The following is provided to support a level of version 4.x backwards compatibility.

Assembly Description NuGet
Beef.Test.NUnit Unit and intra-domain integration testing framework (backwards compatibility only). NuGet version

Samples

The following samples are provided to guide usage:

Sample Description
My.Hr A sample as an end-to-end solution walkthrough to demonstrate the usage of Beef within the context of a fictitious Human Resources solution. The main intent is to show how Beef can be used against a relational database (SQL Server) leveraging both direct ADO.NET (with stored procedures) and Entity Framework (EF) where applicable.
MyEf.Hr A sample as an end-to-end solution walkthrough to demonstrate the usage of Beef within the context of a fictitious Human Resources solution. The main intent is to show how Beef can be used against a relational database (SQL Server) leveraging only Entity Framework (EF).
Cdr.Banking A sample as an end-to-end solution to demonstrate Beef being used to solve a real-world scenario. This demonstrates an implementation of the CDR Banking APIs leveraging a Cosmos DB data source.
Demo A sample as an end-to-end solution to demonstrate the tiering & layering, code-generation, database management and automated intra-domain integration testing. This is primarily used to further test the key end-to-end capabilities enabled by Beef.

Additional documentation

The following are references to additional documentation (these are all accessible via links within this and other documentation):

General

Solution

Code-generation


External links of potential interest

  • Versioning - article, implementation - Beef has no specific support or opinion with respect to versioning approach and/or implementation.
  • Domain-driven design - Wikipedia, Fowler, Microsoft authored articles: article, article, article and article - Beef encourages the DDD approach, and is why Entity naming and convention is foundational within.

License

Beef is open source under the MIT license and is free for commercial use.


Getting started

To start using Beef you do not need to clone or fork the repo; you just need to create a solution with the underlying projects using the prescribed solution structure, including referencing the appropriate NuGet packages. To accelerate this a .NET Core template capability is provided to enable you to get up and running in minutes.

See the following for example end-to-end solution/project creation; each demonstrating the same API functionality leveraging different data sources to accomplish:

Otherwise, follow along with the following sample tutorials that will provide a more in-depth walkthrough solving a defined functional problem:

  • My.Hr - microservice against a SQL Database using both stored procedures and entity framework.
  • MyEf.Hr - microservice against a SQL Database using entity framework.
  • Cdr.Banking - microservice against an Azure CosmosDB data source.
  • Xyz.Legacy - CDC implementation against a legacy database publishing messages to Azure Service Bus.

Contributing

One of the easiest ways to contribute is to participate in discussions on GitHub issues. You can also contribute by submitting pull requests (PR) with code changes.


Coding guidelines

The most general guideline is that we use all the VS default settings in terms of code formatting; if in doubt, follow the coding convention of the existing code base.

  1. Use four spaces of indentation (no tabs).
  2. Use _camelCase for private fields.
  3. Avoid this. unless absolutely necessary.
  4. Always specify member visibility, even if it's the default (i.e. private string _foo; not string _foo;).
  5. Open-braces ({) go on a new line (an if with single-line statement does not need braces).
  6. Use any language features available to you (expression-bodied members, throw expressions, tuples, etc.) as long as they make for readable, manageable code.
  7. All methods and properties must include the XML documentation comments. Private methods and properties only need to specifiy the summary as a minimum.

For further guidance see ASP.NET Core Engineering guidelines.


Tests

NUnit is used for unit testing.

  • Tests need to be provided for every bug/feature that is completed.
  • Tests only need to be present for issues that need to be verified by QA (for example, not tasks).
  • If there is a scenario that is far too hard to test there does not need to be a test for it.
  • "Too hard" is determined by the team as a whole.

Code reviews and checkins

To help ensure that only the highest quality code makes its way into the project, please submit all your code changes to GitHub as PRs. This includes runtime code changes, unit test updates, and updates to the end-to-end demo.

For example, sending a PR for just an update to a unit test might seem like a waste of time but the unit tests are just as important as the product code and as such, reviewing changes to them is also just as important. This also helps create visibility for your changes so that others can observe what is going on.

The advantages are numerous: improving code quality, more visibility on changes and their potential impact, avoiding duplication of effort, and creating general awareness of progress being made in various areas.

beef's People

Contributors

chullybun avatar ciaranodonnell avatar cjgiddings avatar edjo23 avatar israels avatar jamieaitchison avatar karamem0 avatar karpikpl avatar kylemcmaster avatar leandrodotec avatar mdesenfants avatar ostat avatar simke11 avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

beef's Issues

AddGeneratedServices methods causing compilation error when there are no services

E.g. AddGeneratedDataSvcServices, AddGeneratedManagerServices, AddGeneratedDataServices.

Scenario -
None of the entities defined in codegen XML have managers, or data or dataSvc layers. In our particular case code gen in the project was only used to define entity properties, without any operations.

Extensions are generated with empty bodies, e.g.

public static IServiceCollection AddGeneratedManagerServices(this IServiceCollection services)
{
}

Since the methods are expected to return IServiceCollection, this causes compilation error.

In this instance it should probably just return IServiceCollection that was passed in.

AutoInferImplements

Good afternoon from Hamilton, NZ.

I have the latest source up to and including the fix for issue #99

The problem I have found has an example in the Beef.Demo sample code. In Beef.Demo.xml the Product entity has AutoInferImplements set to true and an "Id" field of type int:

<Entity Name="Product" Text="Product" Collection="true" CollectionResult="true" AutoInferImplements="false" WebApiRoutePrefix="api/v1/products" DataSvcCaching="false" AutoImplement="OData" ODataName="TestOData" ODataEntity="Model.Product" ODataCollectionName="Products">
    <Property Name="Id" Text="{{Product}} identifier" Type="int" Immutable="true" UniqueKey="true" DataName="ID" EmitDefaultValue="true" />

However, in the generated code in Entities\Generated\Product.cs the class definition still expects the implementation of IIntIdentifier:

public partial class Product : EntityBase, IIntIdentifier, IUniqueKey, IEquatable<Product>

Looking in \tools\Beef.CodeGen.Core\Config\Entity\EntityConfig.cs we see:

        private void InferImplements()
        {
            if (ImplementsAutoInfer.HasValue && !ImplementsAutoInfer.Value)
                return;

and then

                var iid = id.Type switch
                {
                    "Guid" => "IGuidIdentifier",
                    "int" => "IIntIdentifier",

I think something is happening in the xml->yaml->json translation. Maybe do do with here:
(ConfigType.Entity, ConfigurationEntity.Entity, "AutoInferImplements", "implementsAutoInfer"),
and/or here:

        [JsonProperty("autoInferImplements", DefaultValueHandling = DefaultValueHandling.Ignore)]
        [PropertySchema("Entity", Title = "Indicates whether to automatically infer the interface implements for the entity from the properties declared.",
            Description = "Will attempt to infer the following: `IGuidIdentifier`, `IIntIdentifier`, `IStringIdentifier`, `IETag` and `IChangeLog`. Defaults to `true`.")]
        public bool? ImplementsAutoInfer { get; set; }

Cheers,
Damian.

CreateJsonResultAndETag( ) should return a consistent result.

What is wrong?

When an entity does not implement IEtag, given 2 different calls that produce the same collection, CreateJsonResultAndETag( ) function from Beef.AspNetCore.WebApi.WebApiActionBase class returns different ETag values when API is called with ExcludeFields and returns the same ETag when API is called without ExcludeFields.

What is expected?

EDIT:
Expected to return the same ETag for the collection when entity does not implement IETag and returns a calculated ETag when entity implements IETag.

or

WebApiGet<TResult, TColl, TEntity> should not call CreateJsonResultAndETag( ) and should not set ETag header when items from a Collection do not implement IETag.

How to reproduce:

codegen xml:

<Entity Name="Contact" Collection="true" CollectionResult="true" WebApiRoutePrefix="api/v1/contacts" 
          AutoImplement="Database" Implements="Changelog">
    <Property Name="Id" Type="Guid" UniqueKey="true"/>
    <Property Name="Name" Type="string" DataName="ContactTypeCode" />
 <Operation Name="GetByArgs" OperationType="GetColl" PagingArgs="true">
      <Parameter Name="search" Type="string"/>
    </Operation>
</Entity>

unit test:

Assume the database has 2 contacts: "Mary Flower" and "Mary Smith".

[Test]
        public void A152_GetByArgs_CheckETagWithoutAddingPatient_WithExcludeFields()  
        {

            var contactArgs = "Mary" ;
            var pagingArgs = new Beef.Entities.PagingArgs { IsGetCount = true };
            var webReqOptions = new Beef.WebApi.WebApiRequestOptions().Exclude("changeLog");

            var originalEtag = AgentTester.Create<ContactAgent, ContactCollectionResult>()
                .ExpectStatusCode(HttpStatusCode.OK)
                .Run((a) => a.Agent.GetByArgsAsync(contactArgs, pagingArgs, webReqOptions)).Response.Headers.ETag;

 /****

Update contact name to "Mary Anne"

(details hidden for brevity)
******/


            var newEtag = AgentTester.Create<ContactAgent, ContactCollectionResult>()
                .ExpectStatusCode(HttpStatusCode.OK)
                .Run((a) => a.Agent.GetByArgsAsync(contactArgs, pagingArgs, webReqOptions)).Response.Headers.ETag;

//This test passes
            Assert.AreNotEqual(originalEtag, newEtag, "ETag should not be the same after an update.");
        }

        [Test]
        public void A153_GetByArgs_CheckETagWithoutAddingPatient_WithAllFields()
        {
            var contactArgs = "Mary" ;
            var pagingArgs = new Beef.Entities.PagingArgs { IsGetCount = true };
 
            var originalEtag = AgentTester.Create<ContactAgent, ContactCollectionResult>()
                .ExpectStatusCode(HttpStatusCode.OK)
                .Run((a) => a.Agent.GetByArgsAsync(contactArgs, pagingArgs)).Response.Headers.ETag;
 /****

Update contact name to "Mary Anne"

(details hidden for brevity)
******/
            var newEtag = AgentTester.Create<ContactAgent, ContactCollectionResult>()
                .ExpectStatusCode(HttpStatusCode.OK)
                .Run((a) => a.Agent.GetByArgsAsync(contactArgs, pagingArgs)).Response.Headers.ETag;

//This test fails
            Assert.AreNotEqual(originalEtag, newEtag, "ETag should not be the same after an update.");
        }

_createOnAfterAsync var removed from code gen DataSvc when DataSvcCustom="true" on operation

When DataSvcCustom="true" is set on an operation,

private static readonly Func<Entity, Task> _createOnAfterAsync; 

is removed from generated DataSvc class, but is still used in the generated method, e.g.

public static Task CreateAsync(Entity value)
{
	return DataSvcInvoker.Default.InvokeAsync(typeof(EntityDataSvc), async () => 
	{
		await CreateOnImplementationAsync(value);
		if (_createOnAfterAsync != null) await _createOnAfterAsync(value);
	});
}

This results in _createOnAfterAsync having to be declared in custom partial DataSvc class.

_createOnAfterAsync declaration should either stay in code gen'd DataSvc file, or remove

if (_createOnAfterAsync != null) await _createOnAfterAsync(value);

from code gen'd method and let it be added in custom DataSvc method implementation if needed.

Operation definition for the above scenario:

<Operation Name="Create" OperationType="Custom" AutoImplement="None" WebApiMethod="HttpPost" ValueType="Reservation" WebApiStatus="Accepted" DataSvcCustom="true">

Can't access MinCount and MaxCount validators for a collection

Hi,
I'm trying to use a Beef Validation element for checking minimum and maximum counts in Collections. See https://github.com/Avanade/Beef/blob/master/docs/Beef-Validation.md but i don't seem to have access to the properties it mentions here. (see under Rules--> CollectionRule --> MinCount, MaxCount)
I would expect the syntax to chain like this Property(x => x.Items).Collection(item: new CollectionRuleItem<MyItemField>(MyItemFieldValidator.Default)).MaxCount(100); but the MaxCount property isn't being recognised. Am I using the syntax correctly? Are there any sample code available where one is trying to restrict the size of a collection to at least some MinCount value and no more than MaxCount items in the collection ?

Add global:: to SuppressMessage attribute in generated Data classes

We've got a domain named XYZ.System.
Data classes have SuppressMessage attribute is fully qualified, System.Diagnostics.CodeAnalysis.SuppressMessage.
This causes compiler error "The type or namespace name 'Diagnostics' does not exist in the namespace 'XYZ.System'".
Changing it global::System.Diagnostics.CodeAnalysis.SuppressMessage would resolve this issue.

CodeGen does not return $lastexitcode 1 when error happens and errors are not written to StdErr

Expected behavirour:

When an error happens, a $lastexitcode should be returned and error message should be written to stdErr. At the moment it returns 0 when it succeeds or errors.

Extra background:

This will allow running code gen in automated builds to check for things like:

  • Changes to xml files have been committed without committing generated files.
  • Changes to database were made without re-generating stored procedures.

Occurs in version: 3.1.20

How to reproduce:
In Powershell:

PS>dotnet run all --expectNoChanges 
...
  Template: EntityGrpcServiceAgent_cs.xml (Common/Grpc/ServiceAgents)
   [Files: Unchanged = 0, Updated = 0, Created = 0]
An unexpected exception occurred during the 'Run' phase: Unexpected changes detected; one or more files were created and/or updated. Exception: Beef.CodeGen.CodeGenException: Unexpected changes detected; one or more files were created and/or updated.
   at Beef.CodeGen.CodeGenExecutor.OnRunAsync(ExecutorRunArgs args)
   at Beef.Executors.ExecutorBase.RunAsync(ExecutorRunArgs args)
Executor '0e879bbc-8ef7-4841-ac24-f20d0329fe0e' stop as a result of an unhandled exception: Unexpected changes detected; one or more files were created and/or updated. Exception: Beef.CodeGen.CodeGenException: Unexpected changes detected; one or more files were created and/or updated.
   at Beef.CodeGen.CodeGenExecutor.OnRunAsync(ExecutorRunArgs args)
   at Beef.Executors.ExecutorBase.RunAsync(ExecutorRunArgs args)
   at Beef.Executors.ExecutionManager.<>c__DisplayClass17_1.<<Create>b__2>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Beef.Executors.Executor.<>c__DisplayClass34_0.<<RunWrapperAsync>b__0>d.MoveNext()

PS> $lastexitcode
0

or

bring database down and run:
dotnet run database

Add codegen support for build pipeline no-op

We've noticed on a number of occasions that we can seemingly forget to commit changes to codegen files and still have a functioning build.
In what is thought to be a small improvement, we'd like to run the codegen as part of our build pipeline expecting no changes to be generated. In the off chance we have missed something the codegen would say "sorry, expected no changes and there were changes" providing fast feedback to the dev on what they'd missed.
A sample of what this extension to the codegen might look like can be found here: master...evanon:master
Feedback welcome

Beef DB generation fails on Linux/OSX

Steps to reproduce

  1. clone the repo
  2. execute:
 ~/projects/Beef/samples/Demo/Beef.Demo.Database     master  
   dotnet run all

Expected:
DB updated with schema and ref data

Actual:
Error:
The directory '.\..' does not exist.

Analysis:
Path reference does not work on OSX:
/Beef/tools/Beef.CodeGen.Core/CodeGenConsoleWrapper.cs

string outDir = ".\\.."

Replacing outDir with ./.. solves the issue on OSX.

@chullybun - is there a way to override that param?
passing -o causes this error "Unrecognized option '-o'"

Codegen defaults the class authorised attribute to AllowAnonymous when not provided

Previously when the Codegen WebApiAuthorize attribute was not provided, it didn't set the authorised on the controller class. This allowed us to override individual methods to specify different policies at the method level. Now it appears that if no value is provided the "AllowAnonymous" attribute is appended to the class which bypasses any policy authorisation at the method level. This opens up all the methods within the class to unauthorised access.

HTTP Location Header

It is generally accepted that an API should update the HTTP Location header on the response when returning an HTTP status code of 201 (this is the default when performing a create) or 202. Beef will be extended to enable support.

For an Operation (within the code generation configuration) a new WebApiLocation property is added. This will allow the specification of the Relative URI. The uses similar formatting to the WebApiRoute. The response value is accessed using r. notation to access underlying properties; for example {r.Id} or person/{r.Id}. The Entity.WebApiRoutePrefix will be prepended automatically; however, to disable set the first character to !, e.g. !person/{r.Id}. The URI can be inferred from another Operation by using a lookup ^; for example ^Get indicates to infer from the named Get operation (where only ^ is specified this is shorthand for ^Get as this is the most common value). The Location URI will ensure the first character is a / so it acts a relative URL absolute path.

For the CodeGeneration and Entity configuration a new WebApiAutoLocation property is added. This indicates whether the HTTP Response Location Header route (Operation.WebApiLocation) is automatically inferred. This will automatically set the Operation.WebApiLocation for an Operation named Create where there is a corresponding named Get.

The code that will be generated will look similar to the following:

[HttpPost("")]
[ProducesResponseType(typeof(Person), (int)HttpStatusCode.Created)]
public IActionResult Create([FromBody] Person value)
{
    return new WebApiPost<Person>(this, () => _manager.CreateAsync(WebApiActionBase.Value(value)),
        operationType: OperationType.Create, statusCode: HttpStatusCode.Created, alternateStatusCode: null, locationUri: (r) => new Uri($"/api/v1/persons/{r.Id}", UriKind.Relative));
}

The HTTP response will then include the relative Location header similar to: /api/v1/persons/8e5d038d-fa88-eb11-a1bf-4074e091b64d.

SlidingCachePolicy not caching after first expiry

@ostat raised an issue via a pull request related to the SlidingCachePolicy not functioning correctly after first expiry; is resulting in a get on each usage after said first expiry - i.e. cache does not cache after first expiry. This was observed in ReferenceDataCache and further validated in ReferenceDataMultiTenantCache.

ExecutionContext.Current.CorrelationId only set for http requests?

Ran into an issue with integration tests, correlation id and event publishing.
When I have a test that uses an agent tester, execution context correlation id gets set as expected and everything works ok.
If i try to run a test that doesn't use agent tester and directly calls manager or data method, execution context correlation id value is null. If that method also publishes an event, it throws an exception "The ExecutionContext.CorrelationId must be set for the ExpectEventPublisher to function conrrectly."
Is this intended behaviour?
I can get around the exception by explicitly setting correlation id value in test, but this is possibly not the right way to do it. Doco says not to set correlation id directly, as its set by the framework.

DataSvcCaching="false" on entity still attempts to use cache without providing IRequestCache

Entity definition has DataSvcCaching="false"

<Entity Name="User" Text="User" WebApiRoutePrefix="api/v1" Collection="true" CollectionResult="true" AutoImplement="Database" DatabaseSchema="dbo" DataSvcCaching="false">

Operation example:

<Operation Name="Get" WebApiAuthorize="AllowAnonymous" Text="Gets the specified {{User}} record" OperationType="Get" UniqueKey="true" AutoImplement="Database" WebApiRoute="user/{id}"/>

Generated code still uses _cache, e.g.

public Task<User?> GetAsync(Guid id)
        {
            return DataSvcInvoker.Current.InvokeAsync(this, async () =>
            {
                var __key = new UniqueKey(id);
                if (_cache.TryGetValue(__key, out User? __val))
                    return __val;

                var __result = await _data.GetAsync(id).ConfigureAwait(false);
                _cache.SetValue(__key, __result);
                return __result;
            });
        }

but since there is no

using Beef.Caching;

and

private readonly IRequestCache _cache;

in generated code, it causes compilation error.

Validation of a Collection for duplicate items

I am trying to validate that a collection of objects does not contain duplicates, i.e., if collection A has objects with properties x,y,z, that for any members in the collection e.g. a,b,c, that no duplicates exist, i.e. a.x==b.x && a.y==b.y && a.z==b.z or the same for any other element comparison. Is there a simple way to do this within the validator? I currently have a validator property like Property(x => x.Items).Collection( item: new CollectionRuleItem<MyElementCollection>(MyElementCollectionValidator.Default), minCount: 0, maxCount: 100); but can't seem to find a "noDuplicates" or similar. Any assistance here would be much appreciated.

Bug in fnGetUsername

There is a bug in https://github.com/Avanade/Beef/blob/master/tools/Beef.Database.Core/Schema/dbo/Functions/fnGetUsername.sql

It uses convert function incorrectly to read username from session context.

SET @Username = CONVERT(nvarchar, SESSION_CONTEXT(N'Username'));

Username is defined a varchar of length 50 in function:
DECLARE @Username nvarchar(50)

But above usage of Convert shall only use 30 characters by default. It needs to be changed to:
SET @Username = CONVERT(nvarchar(50), SESSION_CONTEXT(N'Username'));

It is documented in Microsoft docs here

Code generated won't compile for get operations that have no parameters

Bit of an unusual scenario but we have a get endpoint that does not have parameters.

This entity xml:

  <Entity Name="Staging" WebApiRoutePrefix="api/v1/staging">
    <Property Name="Id" Type="string" />
    <Property Name="Uri" Type="string" />
    <Property Name="Token" Type="string" />

    <Operation Name="Get" OperationType="Get" AutoImplement="None" />
  </Entity>

Generates fine except for the partial function hooks, they generate with a missing comma.

In StagingManager:

private readonly Func<Staging?Task>? _getOnAfterAsync;

In StagingDataSvc:

private static readonly Func<Staging?Task>? _getOnAfterAsync;

The generated code has to be modified before it can compile.

CDC codegen outputs errant code where there is a single left outer join specified

Where specifying only a single CdcJoin that is not CDC-related; for example Type is Left then the XxxData class has an errant , character output.

<Cdc Name="Xxx" ExcludeColumns="CreateUser, CreateDate, LastUpdateUser, LastUpdateDate">
  <CdcJoin Name="XxxType" Type="Left" IncludeColumns="XxxTypeCode">
    <CdcJoinOn Name="XxxTypeId" />
  </CdcJoin>
</Cdc>

Also, the AddBeefLoggerEventPublisher is adding the service as a singleton, versus scoped. This is causing the CDC background services to fail.

Stored procedure generation fails for tables with CreatedAt column

Looks like beef is expecting tables to have "CreatedDate" whereas a common column name for audit is "CreatedAt".

Here's the schema:

CREATE TABLE [dbo].[Contact]
(
        ID INT IDENTITY NOT NULL,
        FIRSTNAME NVARCHAR(255) NOT NULL,
        LASTNAME NVARCHAR(255) NOT NULL,
        DOB DATE NULL,
        GENDER CHAR(1) NULL,
        EYECOLOR NVARCHAR(150) NULL,
        EMAIL NVARCHAR(255) NULL,
        PHONENUMBER NVARCHAR(15) NULL,
        [CreatedAt] [date] NOT NULL DEFAULT GETUTCDATE(),
        [UpdatedAt] [date] NULL,
        [CreatedBy] [nvarchar](100) NULL,
        [UpdatedBy] [nvarchar](100) NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Contact] ADD  CONSTRAINT [Contact_PK] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]

and here's the stored procedure that was generated:

CREATE PROCEDURE [dbo].[spContactCreate]
   @ID AS INT = NULL OUTPUT
  ,@FIRSTNAME AS NVARCHAR(255)
  ,@LASTNAME AS NVARCHAR(255)
  ,@DOB AS DATE NULL = NULL
  ,@GENDER AS CHAR(1) NULL = NULL
  ,@EYECOLOR AS NVARCHAR(150) NULL = NULL
  ,@EMAIL AS NVARCHAR(255) NULL = NULL
  ,@PHONENUMBER AS NVARCHAR(15) NULL = NULL
  ,@CreatedAt AS DATE
  ,@UpdatedAt AS DATE NULL = NULL
  ,@CreatedBy AS NVARCHAR(100) NULL = NULL
  ,@ReselectRecord AS BIT = 0
AS
BEGIN
  /*
   * This file is automatically generated; any changes will be lost. 
   */
 
  SET NOCOUNT ON;
  
  BEGIN TRY
    -- Wrap in a transaction.
    BEGIN TRANSACTION

    -- Set audit details.
    EXEC @CreatedDate = fnGetTimestamp @CreatedDate
    EXEC @CreatedBy = fnGetUsername @CreatedBy

    DECLARE @InsertedIdentity TABLE([ID] INT)

    -- Create the record.
    INSERT INTO [dbo].[Contact] (
        [FIRSTNAME]
       ,[LASTNAME]
       ,[DOB]
       ,[GENDER]
       ,[EYECOLOR]
       ,[EMAIL]
       ,[PHONENUMBER]
       ,[CreatedAt]
       ,[UpdatedAt]
       ,[CreatedBy]
    )
    OUTPUT inserted.ID INTO @InsertedIdentity
    VALUES (
        @FIRSTNAME
       ,@LASTNAME
       ,@DOB
       ,@GENDER
       ,@EYECOLOR
       ,@EMAIL
       ,@PHONENUMBER
       ,@CreatedAt
       ,@UpdatedAt
       ,@CreatedBy
    )

    -- Get the inserted identity.
    SELECT @ID = [ID] FROM @InsertedIdentity

    -- Commit the transaction.
    COMMIT TRANSACTION
  END TRY
  BEGIN CATCH
    -- Rollback transaction and rethrow error.
    IF @@TRANCOUNT > 0
      ROLLBACK TRANSACTION;

    THROW;
  END CATCH
  
  -- Reselect record.
  IF @ReselectRecord = 1
  BEGIN
    EXEC [dbo].[spContactGet] @ID
  END
END

Error reported by MSSQL:

Execution failed with: Must declare the scalar variable "@CreatedDate".

Introduce DI to Beef

This issue is to collaborate on making changes to Beef to introduce Dependency Injection (DI) as presented by @edjo23 within his POC. This POC also has suggestions around changing Beef to leverage Handlebars.NET which will managed separately to this change.

This is an initial pass of DI to be used for the generated components only at this stage, being the following:

  • XxxController - constructor change for IXxxManager.
  • XxxManager - constructor changes for IXxxDataSvc.
  • XxxDataSvc - constructor changes for IXxxData, will no longer be static, and will introduce new IXxxDataSvc.

As identified by @edjo23 there can only be a single public constructor, so there needs to be a way to configure this, so the following attributes will be added to the Entity XML:

  • ControllerConstructor="Public|Private|Protected" - (defaults to Public).
  • ManagerConstructor="Public|Private|Protected" - (defaults to Public).
  • DataSvcConstructor="Public|Private|Protected" - (defaults to Public).
  • DataConstructor="Public|Private|Protected" - (defaults to Public).

During discussions @edjo23 suggested a further option to limit the output of the generated extensions code and have the developers explicitly opt-in where required to reduce unnecessary code bloat. This will be all or nothing for the class, not per operation. The following attributes will be added to the Entity XML for those classes that support extensions:

  • ManagerExtensions="true|false" - defaults to false.
  • DataSvcsExtensions="true|false" - defaults to false.
  • DataExtensions="true|false" - defaults to false.

There will be a new ServiceCollectionExtensions class generated for (within the appropriate solution hierarchy) to invoke the IServiceCollection.AddTransient as follows:

  • Api - ./Controllers/ServiceCollectionExtensions.cs - method AddGeneratedControllerServices
  • Business - ./Generated/ServiceCollectionExtensions.cs - method AddGeneratedManagerServices
  • Business - ./DataSvcs/Generated/ServiceCollectionExtensions.cs - method AddGeneratedDataSvcsServices
  • Business - ./Data/Generated/ServiceCollectionExtensions.cs - method AddGeneratedDataServices

Additional changes to be considered:

  • The Api changes above need to occur for the gRPC components also.
  • The API startup.cs will need to be refactored to support DI (including solution templates).
  • The testing and mocking will need to be revisited to work with DI.

Please provide feedback and thoughts. A new branch will be created next week to begin work.

Thanks...

Escape codes in property annotations not generating correctly

I have hit a issue when generating entities with XML escape codes in a properties annotation value. Any escape codes entered for an annotation don't correctly generate. Previously in beef v3 the escape codes would generate code with the value that the escape code was for. Since upgrading to v4, this no longer seems to work. Is this a configuration issue with the code gen file or new intended behaviour?

As an example if I have this code in the codegen file:
<Property Name="MyObject" Type="int" Annotation1="[XmlElement(&quot;MYOBJECT&quot;)]"/>

It generates:
[XmlElement(&quot;MYOBJECT&quot;)]

I expected it to actually generate:
[XmlElement("MYOBJECT")]

Beef Version: V4.1.4

Incomplete GetHashCode() method generated for an entity that does not define any properties

When trying to code gen an entity without any properties (only has operations), GetHashCode() is not generated fully and causes compile error. Missing a return and closing }. It comes out as:

public override int GetHashCode()
        {
            var hash = new HashCode();
        #region ICopyFrom

If a property is added to the entity and re-gen'd, the method is generated correctly, e.g.

public override int GetHashCode()
        {
            var hash = new HashCode();
            hash.Add(TestProp);
            return base.GetHashCode() ^ hash.ToHashCode();
        }

Null Reference Exception in generated Data Service class

Hi, the last update has introduced an issue in the generated Data Service class. The following generated code throws a null reference exception when the result from the data operation is null.

        public static Task<Tenant> GetByHostNameAsync(string? hostName)
        {
            return DataSvcInvoker.Default.InvokeAsync(typeof(TenantDataSvc), async () => 
            {
                var __result = await Factory.Create<ITenantData>().GetByHostNameAsync(hostName).ConfigureAwait(false);
                ExecutionContext.Current.CacheSet(__result.UniqueKey, __result);
                if (_getByHostNameOnAfterAsync != null) await _getByHostNameOnAfterAsync(__result, hostName).ConfigureAwait(false);
                return __result;
            });
        }

This is generated using:

  <Entity Name="Tenant" Text="Tenant configuration" Implements="IETag, IChangeLog, ITenant" Collection="true" WebApiRoutePrefix="api/v1/tenants" CollectionResult="true" 
          AutoImplement="Database" DatabaseSchema="dbo">
    ...
    <Operation Name="GetByHostName" OperationType="Custom" ReturnType="Tenant" WebApiRoute="hostName/{hostName}" WebApiMethod="HttpGet">
      <Parameter Name="HostName" Type="string" />
    </Operation>
  </Entity>

I think this is due to the change to line 481 - https://github.com/Avanade/Beef/blame/master/tools/Beef.CodeGen.Core/Templates/EntityDataSvc_cs.xml#L481

ExecutionContext.Current.CacheSet<{{Operation.ReturnType}}>(__result?.UniqueKey ?? UniqueKey.Empty, __result);

has been changed to:

ExecutionContext.Current.CacheSet(__result.UniqueKey, __result);

Patch cache issue

There is an issue with a Patch operation where a call in the business manager Update method is being made to the Get method to get the current state. This is being done so that the current state can be compared to the requested update state for some validation. What we are finding is that the object being returned by the Get method is the same object as passed to the Update method.

Is there an easy way to default date transforms to DateTimeUtc?

Hi, we would like to enforce all of our date times to be UTC regardless of local time zone. I see we can do this by setting the DateTimeTransform on all dates but was wondering if there was a easy way to make DateTimeUtc the default.

If not can I request this as a feature enhancement, thanks.

DataContextScope.GetContext duplicate key error

I have also created a pull request for this one, when being called in parallel, for example when a generated ReferenceDataProvider executes:

Parallel.ForEach(types, (type, state) => { var x = this[type]; });

a second thread can get past the initial key check in GetContext while the first thread is still waiting for a connection to open resulting in an attempt to add the key twice.

The pull request just contains a lock around this code.

Code gen doesn't generate action when EventSubject is set

Code Gen Code

<Operation Name="Delete" OperationType="Delete" UniqueKey="true" AutoImplement="None" WebApiRoute="{id}" WebApiStatus="Accepted" EventSubject="Reservation">
      <Parameter Name="Id" Type="Guid" IsMandatory="true" />
</Operation>

Generates the following datasvc

public Task DeleteAsync(Guid id)
 {
        return DataSvcInvoker.Current.InvokeAsync(this, async () =>
        {
            await _data.DeleteAsync(id).ConfigureAwait(false);
            await _evtPub.PublishAsync($"Reservation", "", id).ConfigureAwait(false);
            _cache.Remove<Reservation>(new UniqueKey(id));
        });
}

The action string is empty, this only happens when the EventSubject property is set. If the EventSubject is not present, it will set the action to the value of WebApiOperationType or OperationType as expected.

The above code gen did work with previous versions of beef.

Beef Version: V4.1.4

Escaped xml in generated C# code for generic types

If an operation parameter type contains escapable xml characters such as in generic types like List<string> then the handlebars template will escape these characters giving the non-compilable List&lt;string&gt; whenever the parameter type config value is surrounded by two braces {{ParameterType}} instead of three {{{ParameterType}}}. This only occurs in the minority of cases and is corrected by: #78

Ref data code gen for ref data with additional properties puts ConfigureAwait(false) where it shouldn't

When genning ref data with additional properties, e.g.

<Entity Name="MyRefDataEntity" RefDataType="Guid" Collection="true" WebApiRoutePrefix="api/v1/ref/myRefDataEntity" AutoImplement="Database" DatabaseSchema="Ref" ConstType="string" >
    <Property Name="MyProperty" Type="bool" />
</Entity>

Gen'd method for the entity's GetAllAsync produces the following code for getting the property value

item.MyProperty= dr.GetValue<bool>("MyProperty").ConfigureAwait(false);

After code gen is ran, compile fails due to ConfigureAwait(false).

ConfigureAwait(false) is added there via line 154 in ReferenceDataData_cs.xml.

Change to use parallel prefetching of reference data is throwing an exception

Hi. We are in the process of upgrading our Beef packages to version 3 packages. After upgrading we have hit an issue with a change to the generated code for the ReferenceDataProvider.

This maybe a follow on from issue #41.

This generated code for the ReferenceDataProvider, that uses Parallel.ForEach:

        public override Task PrefetchAsync(params string[] names)
        {
            var types = new List<Type>();
            if (names == null)
            {
                types.AddRange(GetAllTypes());
            }
            else
            {
                foreach (string name in names.Distinct())
                {
                    switch (name)
                    {
                        case var n when string.Compare(n, nameof(RefDataNamespace.RefDataA), StringComparison.InvariantCultureIgnoreCase) == 0: types.Add(typeof(RefDataNamespace.RefDataA)); break;
                        // Removed 10 case statements for beverity
                    }
                }
            }

            Parallel.ForEach(types, (type, state) => { var x = this[type]; });
            return Task.CompletedTask;
        }

In this test:

        [Test]
        [AgentTester(TestUser.Admin)]
        public void GetNamed_All()
        {
            var names = ReferenceData.Current.GetAllTypes().Select(x => x.Name).ToArray();

            var r = AgentTester.Create<ReferenceDataAgent>()
                .ExpectStatusCode(HttpStatusCode.OK)
                .Run((a) => a.Agent.GetNamedAsync(names));

            Assert.NotNull(r.Content);
            Assert.AreEqual(JObject.Parse("{ \"content\":" + r.Content + "}")["content"].Children().Count(), names.Length);
        }

Throws this exception:

An unexpected internal server error has occurred. Exception: System.AggregateException: One or more errors occurred. (Invalid operation. The connection is closed.) (Invalid operation. The connection is closed.)
 ---> System.InvalidOperationException: Invalid operation. The connection is closed.
   at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__164_0(Task`1 result)
   at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__274_0(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location where exception was thrown ---
   at Beef.Data.Database.DatabaseCommand.<>c__DisplayClass34_0.<<DbDataReaderWrapperNoResultAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Beef.Data.Database.DatabaseInvoker.WrapInvokeAsync(Object caller, Func`1 func, DatabaseBase param, String memberName, String filePath, Int32 lineNumber)
   at Polly.AsyncPolicy.<>c__DisplayClass40_0.<<ImplementationAsync>b__0>d.MoveNext()

If we modify the generated code to iterate the types in sequence then this works as expected and our test passes.

Require DI for/within Validators

As the Validators can invoke the likes of Manager components which leverage Dependency Injection (DI) the Validators also need to support. As these components can be long running and have an asynchronous requirement the Validators should also support async.

EventPublish="false" on operation still adds event publishing to data service code

Example:

<Entity Name="SiteComponentDeployment" Text="Desired State of Components"  Collection="true" CollectionResult="true" AutoImplement="Database" DatabaseSchema="dbo">
    <Property Name="DeploymentId" Type="Guid" />
    <Property Name="TenantId" Type="Guid" />
    <Property Name="ComponentType" Type="string" />
    <Property Name="ComponentVersion" Type="string" />
    <Property Name="LastKnownState" Type="string" />
    <Property Name="DesiredState" Type="string" />
    
    <Operation Name="Create" OperationType="Create" AutoImplement="None" Validator="SiteComponentDeploymentValidator" EventPublish="false" ExcludeWebApiAgent="true" ExcludeWebApi="true" />
</Entity>

Entity above doesn't have a property that is specified as UniqueKey and Create operation has EventPublish="false".

Generated DataSvc code:

public Task<SiteComponentDeployment> CreateAsync(SiteComponentDeployment value)
{
	return DataSvcInvoker.Current.InvokeAsync(this, async () =>
	{
		var __result = await _data.CreateAsync(Check.NotNull(value, nameof(value))).ConfigureAwait(false);
		await _evtPub.PublishValueAsync(__result, $"System.SiteComponentDeployment.", "Create").ConfigureAwait(false);
		_cache.SetValue(__result.UniqueKey, __result);
		return __result;
	});
}

It shouldn't invoke publisher via _evtPub.PublishValueAsync. Checked against code generated with previous Beef version and publishing wasn't there.

Also, given that no property is set as UniqueKey, should it have _cache.SetValue(__result.UniqueKey, __result);? I can set UniqueKey on a property, as I think it should be there - this is someone else's code that I'm upgrading.

Database access should be asynchronous

Database access is using synchronous operations versus the preferred asynchronous operations; this should result in a performance and scalability improvement.

Overloading WebApiCoreContoller Authorise Attribute

It would be helpful if possible to enhance the generation of the WebApiCoreContollers to allow for overloads to be set on the Authorise Attribute. Currently at the controller level the only options available are "Authorize" and "AllowAnonymous".

The only available work around I've found is at an Operation level where you can set the authorised attribute as any string ("Authorize(Policy = "BearerB2C")") for example and it will be substituted in.

If similar functionality was available at the controller level that would be ideal, otherwise if we don't want it that open, allowing custom "Policy" and "AuthenticationSchemes" overloads to be set would be a good alternative.

Looking to contribute

Hi team, I work with Dan and Marko.

I'm looking to make some contrib PRs to this amazing framework. The first PR I was looking to make is for an extension methods lib for the CodeGen config.

What I mean is, I find the config using YAML and XML strange, and I am wishing to provide a Fluent API style config setup as you find with things like EF fluent API, or LINQ etc.

Are you open to this idea? And, before I start, do you think it would work?

I ask this because there must have been a very good reason why you opted to used YAML, XML as means of defining the Entity configuration for GodeGen and DatabaseGen. Can you see any reason why this feature I am proposing to submit as PR would not work?

In addition to the FluentApi feature, I am planning on building a PostGres implementation as well on the DatabaseGen. I realize the majority of users of Beef would typically use SQL Server, but for personal reasons I feel a PostGres plugin/feature would also be a nice addition.

With these kind of features, would you prefer these be submitted, or would you prefer I create a FORK and implement there, and provide a means for you to properly evaluate first?

Final comments... Beef is Amazing! I look forward to making meaningful contributions to this next level framework. To Eric, you are an elite programmer mate. I learnt a lot reading your code and look forward to learning a lot more. Beef is honestly something I have been looking for a long time, and I'm glad I finally found it.

Cheers

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.