Giter Club home page Giter Club logo

efcore.genericservices's Introduction

EfCore.GenericServices

This library helps you quickly code Create, Read, Update and Delete (CRUD) accesses for a web/mobile/desktop application. It acts as a adapter and command pattern between a database accessed by Entity Framework Core (EF Core) and the needs of the front-end system.

The EfCore.GenericServices library is available on NuGet as EfCore.GenericServices and is an open-source library under the MIT licence. See ReleaseNotes for details of changes and information on each version of EfCore.GenericServices.

List of versions and which .NET framework they support

  • Version 8.0.0: Supports NET 8 only (simpler to update to next NET release)
  • Version 6.?.?: Supports NET 6, 7 and 8
  • Version 5.2.?: Supports NET 5, 6 and 7

There are older versions of the EfCore.GenericServices library, but .NET lower than .NET 5 are not supported by Microsoft.

Documentation and useful articles

The documentation can be found in the GitHub wiki. but the rest of this README file provides a good overview of what the library can do, but here are some articles that give you a detailed description of what the libraray does.

What the library does

This library takes advantage of the fact that each of the four CRUD database accesses differ in what they do, but they have a common set of data part they all use, which are:

a) What database class/table do you want to access? b) What properties in that class/table do you want to access or change?

This library uses DTOs (data transfer objects, also known as ViewModels) plus a special interface to define the class/table and the properties to access. That allows the library to implement a generic solution for each of the four CRUD accesses, where the only thing that changes is the DTO you use.

Typical web applications have hundreds of CRUD pages - display this, edit that, delete the other - and each CRUD access has to adapt the data in the database to show the user, and then apply the changes to the database. So, you create one set of update code for your specific application and then cut/paste + change one line (the DTO name) for all the other versions.

I personally work with ASP.NET Core, so my examples are from that, but it will work with any NET Core type of application (I do know one person have used this libary with WPF).

Limitations with EF Core 5 and beyond

  • The ILinkToEntity<TEntity> interface can't handle an entity class mapped to a multple tables.

Code examples of using EfCore.GenericServices

I personally work with ASP.NET Core, so my examples are all around ASP.NET Core, but EfCore.GenericServices will work with any NET Core type of application (I do know one person have used this libary with WPF).

ASP.NET Core MVC - Controllers with actions

The classic way to produce HTML pages in ASP.NET is using the MVC approach, with razor pages. Here a simple example to show you the basic way to inject and then call the ICrudServices, in this case a simple List.

public class BookController

    private ICrudServices _service;

    public BookController(ICrudServices service)
    {
        _service = service;
    }

    public ActionResult Index()
    {
        var dataToDisplay = _service.ReadManyNoTracked<BookListDto>().ToList()
        return View(dataToDisplay);
    }
    //... etc.

ASP.NET Core - Razor Pages

Here is the code from the example Razor Page application contained in this repo for adding a review to a Book (the example site is a tiny Amazon-like site). This example shows an more complex example where I am updating the Book class that uses a Domain-Driven Design (DDD) approach to add a new review to a book. The code shown is the complete code in the AddReview.cshtml.cs class.

public class AddReviewModel : PageModel
{
    private readonly ICrudServices _service;

    public AddReviewModel(ICrudServices service)
    {
        _service = service;
    }

    [BindProperty]
    public AddReviewDto Data { get; set; }

    public void OnGet(int id)
    {
        Data = _service.ReadSingle<AddReviewDto>(id);
        if (!_service.IsValid)
        {
            _service.CopyErrorsToModelState(ModelState, Data, nameof(Data));
        }
    }

    public IActionResult OnPost()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
        _service.UpdateAndSave(Data);
        if (_service.IsValid)
            return RedirectToPage("BookUpdated", new { message = _service.Message});

        //Error state
        _service.CopyErrorsToModelState(ModelState, Data, nameof(Data));
        return Page();
    }
}

NOTE: If you compare the code above with the AddPromotion or ChangePubDate updates in the same example then you will see they are identical apart from the type of the Data property.

And the ViewModel/DTO isn't anything special (see the AddReviewDto). They just need to be marked with an empty ILinkToEntity<TEntity> interface, which tells GenericServices which EF Core entity class to map to. For more security you can also mark any read-only properties with the [ReadOnly(true)] attribute - GenericServices will never try to update the database with any read-only marked property.

ASP.NET Core Web API

When using ASP.NET Web API then another companion library called EfCore.GenericServices.AspNetCore provides extension methods to help return the data in the correct form (plus other methods to allow unit testing of Web API actions using EfCore.GenericServices).

The code below comes from the example ToDoController example in the EfCore.GenericServices.AspNetCore GitHub repo.

public class ToDoController : ControllerBase
{

    [HttpGet]
    public async Task<ActionResult<WebApiMessageAndResult<List<TodoItem>>>> GetManyAsync([FromServices]ICrudServices service)
    {
        return service.Response(await service.ReadManyNoTracked<TodoItem>().ToListAsync());
    }

    [Route("name")]
    [HttpPatch()]
    public ActionResult<WebApiMessageOnly> Name(ChangeNameDto dto, [FromServices]ICrudServices service)
    {
        service.UpdateAndSave(dto);
        return service.Response();

    //... other action methods removed 
}

Technical features

The EfCore.GenericServices (NuGet, EfCore.GenericServices), is an open-source (MIT licence) netcoreapp2.0 library that assumes you use EF Core for your database accesses. It has good documentation in the repo's Wiki.

It is designed to work with both standard-styled entity classes (e.g. public setters on the properties and a public, paremeterless constructor), or with a Domain-Driven Design (DDD) styled entity classes (e.g. where all updates are done through named methods in the the entity class) - see this article for more on the difference between standard-styled entity classes and DDD styled entity classes.

It also works well with with dependancy injection (DI), such as ASP.NET Core's DI service. But does also contain a simplified, non-DI based configuration system suitable for unit testing and/or serverless applications.

NOTE: I created a similar library for EF6.x back in 2014, which has saved my many months of (boring) coding - on one project alone I think it saved 2 months out of 12. This new version contains the learning from that library, and the new DDD-enabling feature of EF Core to reimagine that library, but in a very different (hopefully simpler) way.

efcore.genericservices's People

Contributors

aaron-kruse avatar dependabot[bot] avatar jonathanquinth avatar jonpsmith avatar martijnschoemaker 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

efcore.genericservices's Issues

GenericService throws an error sometimes while using UpdateAndSaveAsync with method name

This issue for the further investigation for the same issue we discussed via Disqus .
I'm using GenericService library and using DDD aproach and I'm trying to delete review using webapi.

But while calling method of GenericService as following:
await service.UpdateAndSaveAsync(item, nameof(Book.RemoveReview));
it throws

Message: 
   System.InvalidOperationException : Could not find a method of name RemoveReview. The method that fit the properties in the DTO/VM are:
   Match: RemoveReviewWithInclude(Int32 ReviewId)
   Match: RemovePromotion()
 Stack Trace: 
   DecodedDto.FindMethodCtorByName(DecodeName nameInfo, List`1 listToScan, String errorString)
   DecodedDto.GetMethodToRun(DecodeName nameInfo, DecodedEntityClass entityInfo)
   EntityUpdateHandler`1.ReadEntityAndUpdateViaDto(TDto dto, String methodName)
   CrudServicesAsync`1.UpdateAndSaveAsync[T](T entityOrDto, String methodName)
   BookController.DeleteReview(RemoveReviewDto item, ICrudServicesAsync service) line 65
   IntegrationTestBookController.TestDeleteReviewOk() line 73
   --- End of stack trace from previous location where exception was thrown ---

I've BookController.RemoveReview method and I'm explicitly defining to use it by passing second parameter to UpdateAndSaveAsync() but it still throw that error.

I'm not sure what I'm doing wrong. Perhaps some issue with DTO or not sure?
My guess is if we define the method name, it shouldn't try to match other methods. but by looking at the above error it looks like it still try to match the method.

Here's repo to reproduce that issue : Repo
I've also added test for it. you can find it here: Test class
and I'm using this BookController

Method mapping is failing for a simple single field scenario

When calling the code below the following exception is triggered

using System;
using System.ComponentModel;
using System.Threading.Tasks;
using GenericServices;
using GenericServices.PublicButHidden;
using GenericServices.Setup;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace EfCore.GenericServices.Testing
{
    public class SingleFieldMethodMappingTests: IDisposable
    {
        private readonly SQLiteInMemoryOptions<RootContext> _options = new SQLiteInMemoryOptions<RootContext>();

        [Fact]
        public async Task Root_method_call_fails()
        {
            // arrange
            const string item = @"A item!";

            using (var context = new RootContext(_options))
            {
                var utData = context.SetupSingleDtoAndEntities<SetItemDto>();
                var service = new CrudServicesAsync(context, utData.ConfigAndMapper);

                // act
                var dto = new SetItemDto
                {
                    Id = 1,
                    Item = item
                };
                await service.UpdateAndSaveAsync(dto);//, nameof(Root.SetItem)); // DEBUG: Exceptions is thrown. It has the same result when excplicitly setting the method name or not
            }
        }

        public void Dispose() => _options?.Dispose();

        public class SetItemDto : ILinkToEntity<Root>
        {
            [ReadOnly(true)]
            public int Id { get; set; }
            public string Item { get; set; }
        }

        public class Root
        {
            public int Id { get; set; }
            public void SetItem(string item, RootContext context = null)
            {
                if (item is null)
                    throw new ArgumentNullException(nameof(item));
                // This method is never called
            }
        }

        public class RootContext : DbContext
        {
            public RootContext(DbContextOptions<RootContext> options) : base(options) { }
            protected override void OnModelCreating(ModelBuilder builder)
            {
                builder.Entity<Root>().Property(e => e.Id).ValueGeneratedNever();
                builder.Entity<Root>().HasData(new Root { Id = 1 });
            }
        }
    }
}
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.0 (64-bit .NET Core 4.6.27617.05)
[xUnit.net 00:00:00.38]   Discovering: readonly-property
[xUnit.net 00:00:00.43]   Discovered:  readonly-property
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.0 (64-bit .NET Core 4.6.27617.05)
[xUnit.net 00:00:00.43]   Starting:    readonly-property
[xUnit.net 00:00:01.82]     readonly_property.RootMethodTest.Root_method_call_fails [FAIL]
[xUnit.net 00:00:01.82]       Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : Delegate 'System.Action<readonly_property.RootMethodTest.SetItemDto,readonly_property.RootMethodTest.Root,readonly_property.RootMethodTest.RootContext>' has some invalid arguments
[xUnit.net 00:00:01.82]       Stack Trace:
[xUnit.net 00:00:01.82]            at CallSite.Target(Closure , CallSite , Object , Object , Object , DbContext )
[xUnit.net 00:00:01.82]            at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid4[T0,T1,T2,T3](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
[xUnit.net 00:00:01.82]            at GenericServices.Internal.LinqBuilders.BuildCall.RunMethodViaLinq(MethodInfo methodInfo, Object dto, Object entity, List`1 propertyMatches, DbContext context)
[xUnit.net 00:00:01.82]            at System.Dynamic.UpdateDelegates.UpdateAndExecute6[T0,T1,T2,T3,T4,T5,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
[xUnit.net 00:00:01.82]            at GenericServices.Internal.MappingCode.EntityUpdateHandler`1.ReadEntityAndUpdateViaDto(TDto dto, String methodName)
[xUnit.net 00:00:01.82]            at GenericServices.PublicButHidden.CrudServicesAsync`1.UpdateAndSaveAsync[T](T entityOrDto, String methodName)
[xUnit.net 00:00:01.82]         c:\src\local\readonly-property\DddTest copy.cs(33,0): at readonly_property.RootMethodTest.Root_method_call_fails()
[xUnit.net 00:00:01.82]         --- End of stack trace from previous location where exception was thrown ---
[xUnit.net 00:00:01.83]   Finished:    readonly-property
----- Test Execution Summary -----

readonly_property.RootMethodTest.Root_method_call_fails:
    Outcome: Failed
    Error Message:
    Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : Delegate 'System.Action<readonly_property.RootMethodTest.SetItemDto,readonly_property.RootMethodTest.Root,readonly_property.RootMethodTest.RootContext>' has some invalid arguments
    Stack Trace:
       at CallSite.Target(Closure , CallSite , Object , Object , Object , DbContext )
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid4[T0,T1,T2,T3](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
   at GenericServices.Internal.LinqBuilders.BuildCall.RunMethodViaLinq(MethodInfo methodInfo, Object dto, Object entity, List`1 propertyMatches, DbContext context)
   at System.Dynamic.UpdateDelegates.UpdateAndExecute6[T0,T1,T2,T3,T4,T5,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
   at GenericServices.Internal.MappingCode.EntityUpdateHandler`1.ReadEntityAndUpdateViaDto(TDto dto, String methodName)
   at GenericServices.PublicButHidden.CrudServicesAsync`1.UpdateAndSaveAsync[T](T entityOrDto, String methodName)
   at readonly_property.RootMethodTest.Root_method_call_fails() in c:\src\local\readonly-property\DddTest copy.cs:line 33
--- End of stack trace from previous location where exception was thrown ---
    
Total tests: 1. Passed: 0. Failed: 1. Skipped: 0

The complete solution code can be seen at https://gist.github.com/jenseralmeida/b299a11de8e5b281e7af39e83838ae4c

How to manage Unitofwork

I've implement two different service, How to manage unit of work?

Any sample code would be helpful.

Use of Service Classes

Hi @JonPSmith,
Can you please clarify how you are injecting (if you are even using) the Service interfaces inside the ServiceLayer/HomeController?

namespace ServiceLayer.HomeController { public interface IListBooksService { Task<IQueryable<BookListDto>> SortFilterPage (SortFilterPageOptions options); } }

All I see is you're referencing ICrudServices in your PageModel classes. You are not using any of these interfaces.

public class IndexModel : PageModel { private readonly ICrudServices _service; public IndexModel(ICrudServices service) { _service = service; }

I noticed the same in your other repo JonPSmith/EfCore.GenericServices.AspNetCore where in your Api Controller you are injecting ICrudServices or ICrudServicesAsync. You're not using any other service.

public async Task<ActionResult<WebApiMessageAndResult<TodoItemHybrid>>> GetSingleAsync(int id, [FromServices]ICrudServicesAsync service) { return service.Response(await service.ReadSingleAsync<TodoItemHybrid>(id)); }

My guess is these are examples for a separate article you've written.

[Question] Loading a Aggregate Root entity

Hi, JonPSmith
Reading your EF Core book and planning to start a new project with EF core and follow DDD pattern. This library is useful and I plan to use it in the project. However, I have a question regarding to loading the Aggregate Root from Db we need to load all linking entity as well so when executing the IQuerydable we need to include all linking properties. Could you please recommend where should I place the including logic?

Thanks and Regards
Duy

Question: Why is the DbContext initialized during registration

Hi,

Why is the DbContext initialized during configuration of the service? This makes it hard to use GenericServices for DbContexts which overrides the void OnConfigure(DbContextBuilder optionsBuilder) method in situations where the DbContext is configured dynamically, ex. where the connection string is determined based on the UserClaims during an request.

Thanks

UpdateAndSaveAsync() returns Success but throwns Object is not registered as entity class

Hey,

I am currently working on my side project and found this library after reading your book.
Everything seems to work fine but for some reason, the UpdateAndSaveAsync() method returns success but then later EF Core throws The class Object is not registered as entity class in your DbContext PanelContext.
Maybe you have a clue what's going on there because I had no luck debugging this issue.
This is my Setup:

// Update Method input is a dto
await service.UpdateAndSaveAsync(input);
if(!service.IsValid)
    context.ReportError(service.GetAllErrors());
return service.Message;
// Dto
public class UpdateAppInDto : ILinkToEntity<App>
{
    public int AppId { get; set; }
    [Required(ErrorMessage = "Name is missing !")]
    public string Name { get; set; }
    public bool Active { get; set; } = false;
    public bool InviteNeeded { get; set; } = false;
    public bool AvailableForStaff { get; set; } = false;
    public bool AvailableForAdmins { get; set; } = true;
}
// App
public class App
{
    public int AppId { get; set; }
    public string Name { get; set; }
    public bool Active { get; set; }
    public bool InviteNeeded { get; set; }
    public bool AvailableForStaff { get; set; }
    public bool AvailableForAdmins { get; set; }
}
// Startup block 
// GenericServices configuration
services.GenericServicesSimpleSetup<PanelContext>(Assembly.GetAssembly(typeof(UpdateAppInDto)));

After calling the update method as shown in the first snipped the value service.Message is Success but then when my methods returns the value of service.Message an exception if thrown
The class Object is not registered as entity class in your DbContext PanelContext.. My guess would be that the AutoMapper fails but there a no other errors and the fact that the Message is set to Success is weird.
Would really appreciate a hint.

Regards Artur

Writing to the response body is invalid for responses with status code 204.

Using in .NET Core 3.1

EfCore.GenericBizRunner 4.0.0
EfCore.GenericServices.AspNetCore 4.0.0

The following code

        [HttpGet("{id}", Name = "GetSingleRelation")]
        public async Task<ActionResult<WebApiMessageAndResult<RelationEntity>>> GetSingleAsync(Guid id, [FromServices]ICrudServicesAsync<DataDbContext> service)
        {
            var relation = await service.ReadSingleAsync<RelationEntity>(id);
            return service.Response(relation);
        }

service.Response(null) says

This will return a result value, with the status Message 1. If there are no errors and the result is not null it will return a HTTP 200 response plus a json containing the message from the status and the results object 2. If there are no errors but result is null it will return a HTTP 204 (NoContent) with the status Message 3. If there are errors it returns a HTTP 400 with the error information in the standard WebAPI format

However null results into the following exception:

System.InvalidOperationException: Writing to the response body is invalid for responses with status code 204.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowWritingToResponseBodyNotSupported()
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.FirstWriteAsyncInternal(ReadOnlyMemory1 data, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.FirstWriteAsync(ReadOnlyMemory1 data, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.WritePipeAsync(ReadOnlyMemory1 data, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.WriteAsync(ReadOnlyMemory1 source, CancellationToken cancellationToken)
at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|21_0(ResourceInvoker invoker, IActionResult result)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

I have removed all formatters and just using a clean "UseMVC()" in my startup.

Unable to find static ctor

I have a static ctor IStatusGeneric <T> CreateT() but when I debug it, it fails to find one regardless of whether I type the string ctor name (eg CreateUser or CreateUser(5)) or leave it blank. Surprisingly, entityInfo object lists out PublicStaticCreatorMethod as that method. It also has CanBeCreatedByCtorOrStatiMethod as true and rightly lists 0 public ctors.

Entity User is Hybrid with 3 methods, 0 public ctors, and 1 static class factories.

But nameInfo has Name property always set to null and that is where it throws an Exception.
This is where it fails to find.

 public MethodCtorMatch GetCtorStaticCreatorToRun(DecodeName nameInfo, DecodedEntityClass entityInfo)
    {
      if (nameInfo.NameType != DecodedNameTypes.NoNameGiven)
        return DecodedDto.FindMethodCtorByName(nameInfo, this._matchedCtorsAndStaticMethods, "ctor/static methods");
      MethodCtorMatch defaultSetterMethod = this.GetDefaultSetterMethod(this._matchedCtorsAndStaticMethods, "ctor/static method");
      if (defaultSetterMethod != null)
        return defaultSetterMethod;
      throw new InvalidOperationException(string.Format("The entity class {0} did find an ctor/static method that matches the {1}.", (object) entityInfo.EntityType.Name, (object) this.DtoType.Name) + " It only links ctor/static methods where ALL the method's parameters can be fulfilled by the DTO/VM non-read-only properties. The possible matches are \n" + string.Join<MethodCtorMatch>("\n", (IEnumerable<MethodCtorMatch>) this._allPossibleCtorsAndStaticMatches));
    }

I've tried by adding PerDtoConfig mapping and removing it. As a workaround I am thinking of directly calling the static ctor from my controller.

NullReferenceException using ILinkToEntity and embedded resources for CosmosDb

Using CosmosDb with Entity Framework Core it is not possible to use nested resources. Having the following code:

    public class Person : EntityEvents
    {
        public string TenantId { get; private set; }
        public Guid PersonId { get; private set; }
        public string? CallName { get; private set; }
        public string? FirstName { get; private set; }
        public string? Initials { get; private set; }
        public string? Prefix { get; private set; }
        public string LastName { get; private set; }

        public PersonAddress Address { get; private set; }

#nullable disable
        public Person() { }
#nullable restore

        public Person(Guid tenantId, Guid personId, string? callName, string? firstName, string? initials, string? prefix, string lastName,
            string? street = null, string? houseNumber = null, string? houseNumberAddition = null, string? city = null, string? postalCode = null, string? country = null)
        {
            TenantId = tenantId.ToString();
            PersonId = personId;
            CallName = callName;
            FirstName = firstName;
            Initials = initials;
            Prefix = prefix;
            LastName = lastName;

            Address = new PersonAddress(street, houseNumber, houseNumberAddition, city, postalCode, country);
        }
    }

    public class PersonAddress
    {
        public string? Street { get; private set; }
        public string? HouseNumber { get; private set; }
        public string? HouseNumberAddition { get; private set; }
        public string? City { get; private set; }
        public string? PostalCode { get; private set; }
        public string? Country { get; private set; }

#nullable disable  
        public PersonAddress() { }
#nullable  restore

        public PersonAddress(string? street, string? houseNumber, string? houseNumberAddition, string? city, string? postalCode, string? country)
        {
            Street = street;
            HouseNumber = houseNumber;
            HouseNumberAddition = houseNumberAddition;
            City = city;
            PostalCode = postalCode;
            Country = country;
        }
    }

    public class PersonTypeConfiguration : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
            builder.HasPartitionKey(x => x.TenantId);
            builder.HasKey(x => x.PersonId);
            builder.OwnsOne(x => x.Address);
        }
    }

With some Dto's

    public class PersonDto : ILinkToEntity<Domain.AggregatesModel.PersonAggregate.Person>
    {
        [ReadOnly(true)]
        public Guid PersonId { get; set; }
        public string? CallName { get; set; }
        public string? FirstName { get; set; }
        public string? Initials { get; set; }
        public string? Prefix { get; set; }
        public string LastName { get; set; }

        public PersonAddressDto Address { get; set; }
    }

    public class PersonAddressDto : ILinkToEntity<PersonAddress>
    {
        public string? Street { get; set; }
        public string? HouseNumber { get; set; }
        public string? HouseNumberAddition { get; set; }
        public string? City { get; set; }
        public string? PostalCode { get; set; }
        public string? Country { get; set; }
    }

    public class PersonDtoConfig : PerDtoConfig<PersonDto, Domain.AggregatesModel.PersonAggregate.Person>
    {
        public override Action<IMappingExpression<Domain.AggregatesModel.PersonAggregate.Person, PersonDto>> AlterReadMapping =>
            cfg => cfg
                .ForMember(x => x.Address, x =>
                    x.MapFrom(person => person.Address));
    }

EF does not allow to define the entity without a key and using OwnsOne, but I can leave the follow line away.
modelBuilder.Entity<PersonAddress>(pa => pa.HasNoKey());

Setup and then Registering the DtoType's crashes because it expects a primaryKeyProperty on the entity, the entityInfo.PrimaryKeyProperties in DecodedDto contains a property with the value null and then it crashes.

image

InvalidOperationException when saving an object that contains two properties that refer to the same object.

Good afternoon,

I have a problem using your service. The problem is that automapper always creates a new instance of an object, i.e. it makes two instances of one object.

I have done a lot of internet research, but I can't get to the point where it works for me.

As a summary, one can say that in the service:
entity = _mapper.Map(studentclass);
is used, but it should be _mapper.Map(studentclass, entity);.
But to implement this cleverly I did not manage to do so far. It would be really great if you had a hint for me.

Thanks for your efforts:
Jan Kupke

public class TestDbContext : DbContext
    {
        public DbSet<HasTwoNormalEntity> HasTwoNormalEntities { get; set; }
        public DbSet<NormalEntity> NormalEntities { get; set; }
}

namespace Tests.EfClasses
{
    public class HasTwoNormalEntity
    {
        public int Id { get; set; }
        public int NormalEntity1Id { get; set; }
        public NormalEntity NormalEntity1 { get; set; }
        public int NormalEntity2Id { get; set; }
        public NormalEntity NormalEntity2 { get; set; }
    }
}

using GenericServices;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Tests.EfClasses;

namespace Tests.Dtos
{
    public class HasTwoNormalEntityDto : ILinkToEntity<HasTwoNormalEntity>
    {
        public int Id { get; set; }

        public int NormalEntity1Id { get; set; }
        public NormalEntityDto NormalEntity1 { get; set; }

        public int NormalEntity2Id { get; set; }
        public NormalEntityDto NormalEntity2 { get; set; }
    }
}

using System;
using System.Linq;
using System.Threading.Tasks;
using GenericServices;
using GenericServices.PublicButHidden;
using GenericServices.Setup;
using Tests.Dtos;
using Tests.EfClasses;
using Tests.EfCode;
using TestSupport.EfHelpers;
using Xunit;
using Xunit.Extensions.AssertExtensions;

namespace Tests.UnitTests.TestIssues
{
    public class TestSaveOneNewItemWithTheSameExistingItem
    {

        /// <summary>
        /// SaveOneNewItemWithTheSameExistingItem
        /// </summary>
        /// <exception cref="System.InvalidOperationException"></exception>
        [Fact]
        public void ServiceCreateAndSave_SaveANewItemThatContainsTwoReferencesToAnExistingItem_ThrowsExeption()
        {
    
            var options = SqliteInMemory.CreateOptions<TestDbContext>();
            using (var context = new TestDbContext(options))
            {
                //SETUP
                context.Database.EnsureCreated();
                var utData = context.SetupSingleDtoAndEntities< NormalEntityDto>();
                utData.AddSingleDto<HasTwoNormalEntityDto>();
                var service = new CrudServices(context, utData.ConfigAndMapper);

                var entity = new NormalEntityDto { MyString = "42" };
                service.CreateAndSave(entity);
                //VERIFY
                service.IsValid.ShouldBeTrue();

                //ATTEMPT
                var AllNormals = service.ReadManyNoTracked<NormalEntityDto>();
                var fistNormal = AllNormals.First(); //In my Case selected by a Combobox

                HasTwoNormalEntityDto hasTwoNormalEntityDto = new HasTwoNormalEntityDto();
                hasTwoNormalEntityDto.NormalEntity1 = fistNormal;
                hasTwoNormalEntityDto.NormalEntity2 = fistNormal;

                Action act = () => service.CreateAndSave(hasTwoNormalEntityDto);
                InvalidOperationException exception = Assert.Throws<InvalidOperationException>(act);
            }          
        }


        [Fact]
        public void DataContextCreateAndSave_SaveANewItemThatContainsTwoReferencesToAnExistingItem_ThrowsNoExeption()
        {

            var options = SqliteInMemory.CreateOptions<TestDbContext>();
            using (var context = new TestDbContext(options))
            {
                //SETUP
                context.Database.EnsureCreated();
                var utData = context.SetupSingleDtoAndEntities<NormalEntityDto>();
                utData.AddSingleDto<HasTwoNormalEntityDto>();
                var service = new CrudServices(context, utData.ConfigAndMapper);

                var entity = new NormalEntityDto { MyString = "42" };
                service.CreateAndSave(entity);
                //VERIFY
                service.IsValid.ShouldBeTrue();

                //ATTEMPT
                var AllNormals = context.NormalEntities.ToList();
                var fistNormal = AllNormals.First(); //In my Case selected by a Combobox

                HasTwoNormalEntity hasTwoNormalEntity = new HasTwoNormalEntity();
                hasTwoNormalEntity.NormalEntity1 = fistNormal;
                hasTwoNormalEntity.NormalEntity2 = fistNormal;

                context.HasTwoNormalEntities.Add(hasTwoNormalEntity);
                var status = context.SaveChangesWithValidation();

                //VERIFY
                status.IsValid.ShouldBeTrue(status.GetAllErrors());

            }


        }

    }

}

I tried to overwrite the update method in the context class with the following code, but this did not lead to a solution:

  public override EntityEntry<TEntity> Update<TEntity>(TEntity entity) where TEntity : class
        {
            if (entity == null)
            {
                throw new System.ArgumentNullException(nameof(entity));
            }

            var type = entity.GetType();
            var et = this.Model.FindEntityType(type);
            var key = et.FindPrimaryKey();

            var keys = new object[key.Properties.Count];
            var x = 0;
            foreach (var keyName in key.Properties)
            {
                var keyProperty = type.GetProperty(keyName.Name, BindingFlags.Public | BindingFlags.Instance);
                keys[x++] = keyProperty.GetValue(entity);
            }

            var originalEntity = Find(type, keys);
            if (Entry(originalEntity).State == EntityState.Modified)
            {
                return base.Update(entity);
            }

            Entry(originalEntity).CurrentValues.SetValues(entity);
            return Entry((TEntity)originalEntity);
        }

Also I have found a class to address this problem, but I haven't found a way to integrate it into the service.

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace GenericServices.PublicButHidden
{
    public static class EfObjectUpdaterExtentions
    {
        // Used to update entities and avoid 'The instance of entity type 'X' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.' error
        public static void UpdateEntity<T>(
            this DbContext context,
            T existingEntity,
            T newEntity,
            Type[] typesToIgnore = null,
            IEnumerable<Expression<Func<T, object>>> propertiesToIgnore = null
        ) where T : class
        {
            using (var objectUpdater = new EfObjectUpdater())
            {
                objectUpdater.UpdateEntity(context, existingEntity, newEntity,
                    typesToIgnore, null);// propertiesToIgnore?.Select(p => p.GetPropertyFromExpression()).ToArray());
            }
        }

        public static bool IsBasicProperty(this PropertyInfo p)
        {
            return p.IsSpecialName == false && p.GetIndexParameters().Length == 0
            && p.CanRead && p.IsSpecialName == false;
        }
        public static List<Object> ToObjectList(this IEnumerable enumerable)
        {
            return enumerable.Cast<object>().ToList();
        }
    }
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Interface, Inherited = true)]
    public sealed class PersistentAttribute :
    Attribute
    { }

    public sealed class IgnoreMappingAttribute :
  Attribute
    { }


    public class EfObjectUpdater : IDisposable
    {
        private IEnumerable<string> GetIdPropertyNames(DbContext context, object entity)
        {
            if (entity == null) throw new ArgumentException("Parameter cannot be null.");
            var entityType = entity.GetType();

            return context.Model
                .FindEntityType(entityType)
                .FindPrimaryKey().Properties
                .Select(p => p.Name)
                .ToList();
        }

        private IEnumerable<object> GetIdPropertyValues(DbContext context, object obj)
        {
            if (obj == null) throw new ArgumentException("Parameter cannot be null.");
            var objType = obj.GetType();
            var result = new List<object>();

            foreach (var idPropertyName in GetIdPropertyNames(context, obj))
            {
                result.Add(objType.GetProperty(idPropertyName).GetValue(obj));
            }

            if (Attribute.IsDefined(objType, typeof(ReadOnlyAttribute))) return result;
            return result.All(p => p == null || p.Equals(0)) ? Enumerable.Repeat((object)null, result.Count).ToList() : result;
        }

        private static bool PropertiesAreEqual(string propertyName, object obj1, object obj2)
        {
            if (obj1 == obj2) return true;
            if (obj1 == null || obj2 == null) return false;
            var obj1Property = obj1.GetType().GetProperty(propertyName);
            var obj2Property = obj2.GetType().GetProperty(propertyName);
            return obj1Property?.GetValue(obj1)?.Equals(obj2Property?.GetValue(obj2)) ?? false;
        }

        private object Find(DbContext context, IEnumerable list, object objectToFind)
        {
            if (list == null || objectToFind == null) throw new ArgumentException("Parameters cannot be null.");
            var objectToFindPropertyNames = GetIdPropertyNames(context, objectToFind);
            return list.Cast<object>().SingleOrDefault(listObject => objectToFindPropertyNames.All(p => PropertiesAreEqual(p, objectToFind, listObject)));
        }

        private void UpdateProperties(IEnumerable<string> propertyNames, object existingEntity, object newEntity)
        {
            bool ValuesAreEqual(object v1, object v2) => v1 == v2 || (v1?.Equals(v2) ?? false);
            var entityType = existingEntity.GetType();
            foreach (var propertyName in propertyNames)
            {
                var prop = entityType.GetProperty(propertyName);
                if (prop == null) continue;
                var existingEntityValue = prop.GetValue(existingEntity);
                var newEntityValue = prop.GetValue(newEntity);
                if (ValuesAreEqual(existingEntityValue, newEntityValue)) continue;
                prop.SetValue(existingEntity, newEntityValue);
            }
        }

        private readonly IDictionary<object, bool> _visited = new Dictionary<object, bool>();
        private bool WasVisited(object obj)
        {
            return _visited.ContainsKey(obj);
        }

        private void MarkAsVisited(object obj)
        {
            _visited.Add(obj, true);
        }

        public void UpdateEntity<T>(DbContext context, T existingEntity, T newEntity, Type[] typesToIgnore = null, PropertyInfo[] propertiesToIgnore = null) where T : class
        {
            if (existingEntity == null || newEntity == null) throw new NullReferenceException();
            var existingEntityType = existingEntity.GetType();
            var newEntityType = newEntity.GetType();
            if (existingEntityType != newEntityType) throw new InvalidOperationException();

            if (typesToIgnore?.Contains(existingEntityType) ?? false) return;
            if (WasVisited(existingEntity)) return;
            MarkAsVisited(existingEntity);

            var basicProperties = existingEntityType.GetProperties().Where(p =>
                !(propertiesToIgnore?.Contains(p) ?? false) && p.IsBasicProperty()
            ).Select(p => p.Name).ToList();
            var collectionProperties = existingEntityType
                .GetProperties()
                .Where(p =>
                    basicProperties.All(bp => bp != p.Name) &&
                    p.PropertyType
                        .GetInterfaces()
                        .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICollection<>)) &&
                    p.CustomAttributes.All(ca => ca.AttributeType != typeof(IgnoreMappingAttribute))
                )
                .ToList();
            var navigationProperties = existingEntityType
                .GetProperties()
                .Where(p =>
                    basicProperties.All(bp => bp != p.Name) &&
                    collectionProperties.All(cp => cp.Name != p.Name) &&
                    p.CustomAttributes.All(ca => ca.AttributeType != typeof(IgnoreMappingAttribute))
                ).ToList();

            //basic properties
            UpdateProperties(basicProperties, existingEntity, newEntity);

            //collection properties
            foreach (var collectionProperty in collectionProperties)
            {
                var existingCollection = (IList)collectionProperty.GetValue(existingEntity);
                if (existingCollection == null)
                {
                    var collectionType = collectionProperty.PropertyType.GenericTypeArguments.FirstOrDefault();
                    if (collectionType != null)
                    {
                        existingCollection = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(collectionType));
                    }
                    else
                    {
                        existingCollection = new List<object>();
                    }
                    collectionProperty.SetValue(existingEntity, existingCollection);
                }
                var newCollection = ((IList)collectionProperty.GetValue(newEntity))?.Cast<object>().ToList() ?? new List<object>();
                var existingCollectionType = existingCollection.Cast<object>().FirstOrDefault()?.GetType();
                var newCollectionType = newCollection.FirstOrDefault()?.GetType();
                if (typesToIgnore?.Contains(existingCollectionType) ?? false) continue;
                if (typesToIgnore?.Contains(newCollectionType) ?? false) continue;

                foreach (var existingElement in existingCollection.Cast<object>().ToList())
                {
                    if (WasVisited(existingElement)) continue;
                    var elementExistsInTheNewList = Find(context, newCollection, existingElement) != null;
                    if (elementExistsInTheNewList) continue;
                    if (Attribute.IsDefined(existingCollectionType, typeof(PersistentAttribute)))
                    {
                        existingCollection.Remove(existingElement);
                    }
                    else
                    {
                        SetEntityState(context, existingElement, EntityState.Deleted);
                    }
                }
                var additionList = new List<object>();
                foreach (var newElement in newCollection)
                {
                    object existingElementInDbSet = null;
                    try
                    {
                        var dbSet = GetDbSet(context, newCollectionType);
                        existingElementInDbSet = InvokeMethod(dbSet, "Find",
                            GetIdPropertyValues(context, newElement).ToArray());
                    }
                    catch (InvalidOperationException)
                    {
                        var dbSet = GetDbSet(context, newCollectionType);
                        List<Object> allElements = ((IEnumerable)dbSet).ToObjectList();

                        existingElementInDbSet = Find(context, allElements, newElement);
                    }
                    if (existingElementInDbSet == null)
                    {
                        UpdateEntity(context, newElement, newElement, typesToIgnore, propertiesToIgnore);
                        additionList.Add(newElement);
                    }
                    else
                    {
                        UpdateEntity(context, existingElementInDbSet, newElement, typesToIgnore, propertiesToIgnore);
                        var existingElement = Find(context, existingCollection, newElement);
                        if (existingElement == null)
                        {
                            additionList.Add(existingElementInDbSet);
                        }
                        else
                        {
                            existingCollection[existingCollection.IndexOf(existingElement)] = existingElementInDbSet;
                        }
                    }
                }
                //Because the above UpdateEntiy call is recursive, if we add the new elemnets (with ID=0)
                //beforehand, only the first one will get to be added to the list (the other ones are with the same ID and misidentified)
                foreach (var o in additionList)
                {
                    existingCollection.Add(o);
                }
            }

            //navigation properties
            foreach (var navigationProperty in navigationProperties)
            {
                if (propertiesToIgnore?.Any(p => p.Equals(navigationProperty)) ?? false) continue;
                if (typesToIgnore?.Contains(navigationProperty.PropertyType) ?? false) continue;
                var newEntityPropertyValue = navigationProperty.GetValue(newEntity);
                var propertyEntityDbSet = GetDbSet(context, navigationProperty.PropertyType);

                if (newEntityPropertyValue == null)
                {
                    if (Attribute.IsDefined(navigationProperty.PropertyType, typeof(PersistentAttribute)))
                    {
                        navigationProperty.SetValue(existingEntity, null);
                    }
                    else
                    {
                        var existingPropertyValue = navigationProperty.GetValue(existingEntity);
                        if (existingPropertyValue != null)
                            SetEntityState(context, existingPropertyValue, EntityState.Deleted);
                    }
                }
                else
                {
                    var existingEntityPropertyValue = InvokeMethod(propertyEntityDbSet, "Find", GetIdPropertyValues(context, newEntityPropertyValue).ToArray());
                    if (existingEntityPropertyValue == null)
                    {
                        navigationProperty.SetValue(existingEntity, newEntityPropertyValue);
                    }
                    else
                    {
                        navigationProperty.SetValue(existingEntity, existingEntityPropertyValue);
                        UpdateEntity(context, existingEntityPropertyValue, newEntityPropertyValue, typesToIgnore, propertiesToIgnore);
                    }
                }
            }
        }

        private static object GetDbSet(DbContext context, Type type)
        {
            return context.GetType().GetMethod("Set").MakeGenericMethod(type).Invoke(context, new object[0]);
        }

        private static object InvokeMethod(object dbSet, string methodName, object[] parameters)
        {
            return dbSet.GetType().GetMethod(methodName).Invoke(dbSet, new object[] { parameters });
        }

        private static void SetEntityState(DbContext context, object obj, EntityState state)
        {
            context.Entry(obj).State = state;
        }

        public void Dispose()
        {
            _visited.Clear();
        }
    }
}

Exception when no default constructor matches the DTO properties

Hi Jon,

I'm having an exception when attempting to use a DTO to update the DB. It looks like it might have something to do with being unable to find a constructor. I would have expected that it would fall back to Automapper in this scenario. The dtos are DDD style with private parameterless ctors and private setters on the classes. It's my understanding that Automapper can still map these types of classes because it uses reflection. Any insight you have will be greatly appreciated. Thanks.

{"Sequence contains no elements"}
at System.Linq.Enumerable.First[TSource](IEnumerable1 source) at GenericServices.Internal.Decoders.DecodedDto.GetDefaultCtorOrMethod(List1 listToScan, String errorString)
at GenericServices.Internal.Decoders.DecodedDto.GetCtorStaticCreatorToRun(DecodeName nameInfo, DecodedEntityClass entityInfo)
at GenericServices.Internal.MappingCode.EntityCreateHandler1.CreateEntityAndFillFromDto(TDto dto, String methodCtorName) at GenericServices.PublicButHidden.CrudServices1.CreateAndSave[T](T entityOrDto, String ctorOrStaticMethodName)
at CRMTests.DbTests.AccountDtoTests.CanCreateAccountFromAccountDto() in C:***\CRMTests\DbTests\AccountDtoTests.cs:line 59

Request transform Nested Properties from Dto to Entity when creating or updating feature

Let's say, I have a Customer entity as below:

[Table("Customers")]
[DisplayName("The Customer")]
public class Customer
{
    public Customer(string name, Contact generalContact = null)
    {
        Name = name;
        Contact = generalContact ?? new Contact();
    }

    private Customer(){}

    public int Id { get; private set; }

    [MaxLength(200)]
    [Required]
    public string Name { get; private set; }

    [Required]
    public Contact Contact { get; private set; }
}

 [Owned]
public class Contact
{
    [MaxLength(200)]
    [Required]
    public string Name { get; set; }
}

And Here is my Models

public class CustomerDto : ILinkToEntity<Customer>
{
    public int Id { get; set; }

    [MaxLength(200)]
    [Required]
    public string Name { get; set; }

    [Required]
    public ContactModel GeneralContact { get; set; }
}

public class ContactModel:ILinkToEntity<Contact>
{
    [MaxLength(200)]
    [Required]
    public string Name { get; set; }
}

The Generic Service is failed to creating the Customer from Customer Dto as the nested property is not supported yet.

 var cus = new CustomerDto { Name = "New Name", GeneralContact = new ContactModel { Name = "Duy" } };
service.CreateAndSave<Customer>(cus);

Expected: The tested Dto properties shall be transformed to entities by loading the entities from database.

Hope the Generic Service will support nested Dto for creating and updating soon in future.

Async

hi jon. not sure if i should write to you here.. or in your blog.. sorry in advance.

I love your library/framework.. i am starting in ASP.NET Core... and the dbcontext/repo/UoW/Service didnt work in my mind.. i finally found your blog and fall in love.

I am starting using the EFCoreGenericServices.. and would like to know what can i do for the following cases

  1. Service with and without async: You made the CrudService And CrudServiceAsync. What can i do to Implement a CRUDServiceSyncandAsync ( or something like that).

  2. How are you handling ConcurrencyExceptions? All my entities derive from BaseEntity, wich implements a TimeStamp property.
    I am trying to capture this, using two browsers... but i am unable of getting an error

SortPageFilterPageOption does not work as intended

I don't understand how PrevCheckState and NewCheckState is supposed to work when this class is instantiated for every request.

public async Task SetupRestOfDto<T>(IQueryable<T> query)
     {
         NumPages = (int) Math.Ceiling(
             (double) (await query.CountAsync())/PageSize);
         PageNum = Math.Min(
             Math.Max(1, PageNum), NumPages);                                                            

         var newCheckState = GenerateCheckState();
         if (PrevCheckState != newCheckState)
             PageNum = 1;

         PrevCheckState = newCheckState;
     }

     private string GenerateCheckState()
     {
         return $"{(int) FilterBy},{FilterValue},{PageSize},{NumPages}";
     }

PageNum is then always set to 1.

Unable to map read only collections

I don't know if you have already tried this but if you wanted to map reviews in BookListDto it will silently fail.
DataLayer.EfClasses.Book

public IEnumerable<Review> Reviews => _reviews?.ToList();

If you create a property Reviews in BookListDto

public IEnumerable<Review> Reviews {get; private set;}

It will always be null even if you override in AlterReadMapping.

public override Action<IMappingExpression<Book, BookDto>> AlterReadMapping
        {
            get
            {
                return cfg => cfg
                    .ForMember(x => x.Reviews, y => y.MapFrom(p => p.Reviews));
                    // or below
                   // .ForMember(x => x.Reviews, opt => opt.UseDestinationValue());
            }
        }

However, I do notice that you have a way to get Reviews count in BookListDto

.ForMember(x => x.ReviewsCount, x => x.MapFrom(book => book.Reviews.Count()))

QueryTrackingBehavior.NoTracking and Updates

Hi @JonPSmith, I am running into an issue/feature :) with this library. When I wireup my DbContext in startup with the QueryTrackingBehavior.NoTracking then calling subsequent Update methods on the CrudServices nothing gets updated which makes sense. If this is the intent can there be a something in GenericStatus that tells us nothing was affected or indicating the NoTracking status?

NullReferenceException when adding to services with ILinkToEntity<> to an Entity in TPH

I am trying to use Generic Services with an entity that has a table per hierarchy inheritance. It appears that as soon as I run RegisterGenericServices the library throws a NullReferenceException. Is there a workaround, or is there some configuration I am not doing to enable this to work? Any guidance would be appreciated. The stack trace is below:

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=GenericServices
  StackTrace:
   at GenericServices.Internal.Decoders.ParametersMatch.<>c.<get_Score>b__4_0(PropertyMatch x)
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.Average(IEnumerable`1 source)
   at GenericServices.Internal.Decoders.ParametersMatch.get_Score()
   at GenericServices.Internal.Decoders.DecodedDto.<>c.<MatchMethodsToProperties>b__25_2(MethodCtorMatch x)
   at System.Linq.Enumerable.WhereListIterator`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at GenericServices.Internal.Decoders.DecodedDto.MatchMethodsToProperties(DecodedEntityClass entityInfo)
   at GenericServices.Internal.Decoders.DecodedDto..ctor(Type dtoType, DecodedEntityClass entityInfo, IGenericServicesConfig publicConfig, PerDtoConfig perDtoConfig)
   at GenericServices.Internal.Decoders.DecodedDataCache.<>c__DisplayClass4_0.<GetOrCreateDtoInfo>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at GenericServices.Internal.Decoders.DecodedDataCache.GetOrCreateDtoInfo(Type classType, DecodedEntityClass entityInfo, IGenericServicesConfig publicConfig, PerDtoConfig perDtoConfig)
   at GenericServices.Setup.Internal.RegisterOneDtoType..ctor(Type dtoType, Type[] typesInAssembly, IGenericServicesConfig configuration)
   at GenericServices.Setup.Internal.SetupDtosAndMappings.RegisterDtosInAssemblyAndBuildMaps(Assembly assemblyToScan)
   at GenericServices.Setup.Internal.SetupDtosAndMappings.ScanAllAssemblies(Assembly[] assembliesToScan, IGenericServicesConfig config)
   at GenericServices.Setup.ConfigureGenericServices.ScanAssemblesForDtos(IGenericServicesSetupPart1 setupPart1, Assembly[] assembliesToScan)
   at ...

EF Core 3 issue?

Got:

System.MissingMethodException: 'Method not found: 'Boolean Microsoft.EntityFrameworkCore.Metadata.IEntityType.get_IsQueryType()'.'

when doing:

services.GenericServicesSimpleSetup<ApplicationDbContext>(Assembly.GetAssembly(typeof(TestDto)));

How to find entities that have been soft deleted

Hi @JonPSmith
As the QueryFIlter is a feature allow to define a default filter for the query when reading from the database if useful when We are using soft delete mechanism. However when ReadSingle by entity Id it should call IgnoreQueryFilters as we want to load an identify item via the keys.

Shall We call IgnoreQueryFilters by default for ReadSingle? I could able to provide a pull request for this change?

Thank and Regards

Setup with WPF correct?

I wanted to try and use this library for a practice app I am building, using WPF. I plan to eventually change the WPF to a web app, but thought it'd be easier to learn EF core/DI when I can build a quick interface and not have to learn web stuff as well.

Been reading your book and the Dependency Injection in .Net as well, so I am pretty knew to both concepts.

To start, I decided to build a WPF UI (using Caliburn Micro) on top of the example GenericServices project, just to get a hang of everything. I did have to change the target frameworks for both the Service and Data layers to .Net Standard 2.0, but everything still built correctly.

Only question so far is why I needed to add Autofac as a dependency in the WPF project for GenericServices to work? I thought that would be okay just being referenced in GenericServices?
For reference, I'm using SimpleInjector for my DI container.

Here is the configuration code I wrote, is this setup correctly?

        private Container _container;
        protected override void Configure()
        {
            this._container = new Container();
            this._container.Options.DefaultScopedLifestyle = new ThreadScopedLifestyle();
            
            //This is for Caliburn Micro
            this._container.RegisterSingleton<IWindowManager, WindowManager>();
            this._container.RegisterSingleton<IEventAggregator, EventAggregator>();

            this._container.Register<ShellWindowViewModel>();
            this._container.Register<BooksListViewModel>();

            SetupContextAndCrudServices();
            this._container.Verify();
        }

        private void SetupContextAndCrudServices()
        {
            var context = SetupEfContext();
            var crudServices = SetUpCrudServices(context);
            // Does the context even need to be registered? Or can I just register it in CrudServices?
            this._container.Register<EfCoreContext>(SetupEfContext, Lifestyle.Scoped);
            this._container.Register<ICrudServices>(() => SetUpCrudServices(context), Lifestyle.Scoped);
        }

        private EfCoreContext SetupEfContext()
        {
            var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
            var connectionString = connectionStringBuilder.ToString();
            var connection = new SqliteConnection(connectionString);
            connection.Open();
            var options = new DbContextOptionsBuilder<EfCoreContext>();
            options.UseSqlite(connection);

            var context = new EfCoreContext(options.Options);
            context.DevelopmentEnsureCreated();
            context.SeedDatabase(Directory.GetCurrentDirectory());

            return context;
        }

        private ICrudServices SetUpCrudServices(EfCoreContext context)
        {
            // Just wanted to start off with the one, but to add more I would just do
            //     utData.AddSingleDto<>() for each dto to add?
            var utData = context.SetupSingleDtoAndEntities<BookListDto>();
            return new CrudServices<EfCoreContext>(context, utData.ConfigAndMapper);
        }
       
       // More WPF specific stuff 

ArgumentException: Type 'ObserVisionWeb.PaginatedList`1[MyDto]' does not have a default constructor (Parameter 'type')

Hi Jon,
Your book and articles are awesome for a beginner like myself.
I am facing the issue in the title and I cannot find the problem because it happen in between the click on the razorpage button and the beginning of the related method in my model (I put a breakpoint but never reached it).
I must be doing something wrong in my GenericServices or maybe AutoMapper setup.
I am using ReadManyNoTracked to populate the PaginatedList with the Dto.
I added parameterless constructors for related DbContext, the Dto and the related Entity with no result.
Using EfCore.GenericServices (3.2.2)
Thanks
Seb

ICrudServices for updating soft delete property using query filters

Hi Jon,
Thanks for making this library available, it is awesome.

I am trying to change the flag "SoftDelete" to false (This entity has already been soft deleted).
I am able to "get" the Dto using a ProjectFromEntityToDto method with .IgnoreQueryFilters.
I change the value of the SoftDeleted property of this Dto (to false) and my Dto is correct.
When I use service.UpdateAndSave method on this Dto, the SoftDeleted property doesn't get updated to false.
Is there a way to IgnoreQueryFilters when using GenericServices UpdateAndSave?
Thanks

How to register one to many owned types

Is it possible to register a one-to-many relationship of owned types? When I try to register the class, GenericServices throws an error when it cannot find a PK on the owned type.

Updating nested DTO's fails with exception

Hi @JonPSmith ,
i've been off a few days and didn't response.
I just updated
https://github.com/bleissem/TestNestedInDtosIssue13
and adjusted some things to avoid the automapper errormessage.
I think the automapper related errors are now gone.
I also include the GenericServices Code to be able to show you exactly when the test fails and when it does not fail.

For the test:
TestCallControllerMethodDoesNotWorkOk
now the test still fails but this time with:
Message: Microsoft.EntityFrameworkCore.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
---- Microsoft.Data.Sqlite.SqliteException : SQLite Error 19: 'UNIQUE constraint failed: AddressNotOwned.Id'.
This is because when updating an entity there are no navigation properties beeing loaded.

In commit 46c088560e313f5c63fd0ed56d90fd69888eabe5
I modified the function CreateMapper.ReturnExistingEntity in GenericServices so that updating actually works and the unit test passes.
However the unit test: TestCallControllerMethodDoesNotWorkOk fails when using the original code from GenericServices.
as seen in commit 95287da18ed115215df73896b90a71668073090e

Originally posted by @bleissem in #21 (comment)

Object not set to an instance of an object

I am using EfCore.GenericServices 3.1.0.

I started getting a NullReferenceException when configuring generic services, after making a change in my entity.

The exception with stack trace:

System.NullReferenceException: 'Object reference not set to an instance of an object.'
   at GenericServices.Internal.Decoders.ParametersMatch.<>c.<get_Score>b__4_0(PropertyMatch x)
   at System.Linq.Enumerable.Average[TSource](IEnumerable`1 source, Func`2 selector)
   at GenericServices.Internal.Decoders.DecodedDto.<>c.<MatchMethodsToProperties>b__25_2(MethodCtorMatch x)
   at System.Linq.Enumerable.WhereListIterator`1.ToList()
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at GenericServices.Internal.Decoders.DecodedDto..ctor(Type dtoType, DecodedEntityClass entityInfo, IGenericServicesConfig publicConfig, PerDtoConfig perDtoConfig)
   at GenericServices.Internal.Decoders.DecodedDataCache.<>c__DisplayClass4_0.<GetOrCreateDtoInfo>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at GenericServices.Internal.Decoders.DecodedDataCache.GetOrCreateDtoInfo(Type classType, DecodedEntityClass entityInfo, IGenericServicesConfig publicConfig, PerDtoConfig perDtoConfig)
   at GenericServices.Setup.Internal.RegisterOneDtoType..ctor(Type dtoType, Type[] typesInAssembly, IGenericServicesConfig configuration)
   at GenericServices.Setup.Internal.SetupDtosAndMappings.RegisterDtosInAssemblyAndBuildMaps(Assembly assemblyToScan)
   at GenericServices.Setup.Internal.SetupDtosAndMappings.ScanAllAssemblies(Assembly[] assembliesToScan, IGenericServicesConfig config)
   at GenericServices.Setup.ConfigureGenericServices.ScanAssemblesForDtos(IGenericServicesSetupPart1 setupPart1, Assembly[] assembliesToScan)
   at GenericServices.Setup.ConfigureGenericServices.GenericServicesSimpleSetup[TContext](IServiceCollection services, IGenericServicesConfig configuration, Assembly[] assembliesToScan)
   at Api.Startup.RegisterApiDependencies(IServiceCollection services, Assembly appAssembly, Assembly apiAssembly) in C:\Projects\Apps\SpareTime\Api\Startup.cs:line 291
   at Api.Startup.RegisterDependencies(IServiceCollection services, Assembly appAssembly, Assembly apiAssembly) in C:\Projects\Apps\SpareTime\Api\Startup.cs:line 230
   at Api.Startup.ConfigureServices(IServiceCollection services) in C:\Projects\Apps\SpareTime\Api\Startup.cs:line 83

This is how I configure GenericServices:

services.GenericServicesSimpleSetup<AppDbContext>(new GenericServicesConfig
{
   DirectAccessValidateOnSave = true,
   DtoAccessValidateOnSave = true,
   NoErrorOnReadSingleNull = true,
}, apiAssembly, appAssembly);

This is my entity (the relevant parts):

public Location()
{
   Id = Guid.NewGuid();
   CreatedOn = DateTimeOffset.Now;

   _locationFacilityMappings = new HashSet<LocationFacilityMapping>();
}

public Guid Id { get; private set; }

public DateTimeOffset CreatedOn { get; private set; }

private HashSet<LocationFacilityMapping> _locationFacilityMappings;
public IReadOnlyCollection<LocationFacilityMapping> LocationFacilityMappings
   => _locationFacilityMappings?.ToList().AsReadOnly();

This is what I added in my entity:

public void AddFacilities(ICollection<Facility> facilities)
{
   foreach (var facility in facilities)
   {
   	_locationFacilityMappings.Add(new LocationFacilityMapping
   	{
   		Facility = facility,
   		Location = this,
   	});
   }
}

Explicitly Calling Services and Injecting AutoMapper

Hello,

I've been experimenting with your GenericServices platform and so far am really liking it. I like the separation it enforces between data models, services, dtos, and controllers. I do have one concern going forward with it:

The resolving of service methods happens at run-time, and gives little to no visibility to where that method is. This isn't a problem on a small scale, but once the application scales I foresee an issue debugging this.

I've somewhat concocted a solution for this, by providing all services through DI and just injecting the desired service(s) into the controller that will be utilizing it. It's a little more work, but I believe this will allow for better visibility into the connection between the controller and service.

Anyways, to my question. I am a huge fan of the auto-mapping core models to DTOs and back. I'm obviously missing out on that benefit by taking out the auto-resolution of service methods. I've looked into how you're utilizing AutoMapper within your configuration step, and wondered:

Is there a suggested way to gain access and dependecy inject the AutoMapper instance that you're configuring at startup that you setup the ILinkToEntity objects with?

Giving access to this AutoMapper instance will allow for me to map to and from DTOs within my services and hopefully be the last piece to this puzzle. Thanks for your work and your blog posts, they're very insightful and have been great to read.

DTO Not Registered as Valid CrudServices DTO

NullReferenceException: The class ClassDTO is not registered as a valid CrudServices DTO/ViewModel. Have you left off the ILinkToEntity interface?
GenericServices.Internal.Decoders.DecodedDataCache.GetDtoInfoThrowExceptionIfNotThere(Type dtoType)

I have verified that the DTO does have the ILinkToEntity interface implmeneted with the corresponding entity. Any other things I can check? I'm out of ideas at this point.

PerDtoConfig Support For Primitives?

Would it be possible for the PerDToConfig to support primitive value mappings to objects? What I have is a scenario where I pass an ID collection of ints to the client and then when they are sent back I need to map those IDs to an object collection.

Right now there is a type restriction on the PerDtoConfig class so I am not sure what kind of impact a change like this would have.

Issues using with Blazor Server

Im curious if you have tested this library with Blazor server side. I run into issues using crud services in components where a crud service will return an error that gets handled correctly and added to the IStatusGeneric collection. I correct the error and resubmit but the same error is still present. I suspect this is likely due to how the dbcontext/crudservice is scoped to the lifetime of the component. I was curious if you have a way or were looking into using IDbContextFactory as a possible enhancement or addition to the code base.

of type 'System.DBNull' to type 'System.DateTime'

Hallo Jon,

after reading your book, i was thrilled with your approach. Now I've created a simple project for myself, starts properly. I have an entity with a property of the DateTime? Type. I get the exception:
An unhandled exception has occurred while executing the request.
System.InvalidCastException: Unable to cast object of type 'System.DBNull' to type 'System.DateTime'.
at MySqlConnector.Core.Row.GetDateTime(Int32 ordinal) in //src/MySqlConnector/Core/Row.cs:line 358
at MySqlConnector.MySqlDataReader.GetDateTime(Int32 ordinal) in /
/src/MySqlConnector/MySqlDataReader.cs:line 261

Any idea how I can solve this problem?

Disabling errors on Read no result

Is there is way to disable errors on CrudServices.Read()? The reason I ask is I have to perform other CrudServices actions like CreateAndSave() on other entities and the IsValid property will not allow this to happen. I need to say "this is perfectly fine if you do not find any data" .

A use case is I am checking if an employee exists before deciding whether I want to send a duplicate error message or insert. If I cannot find, it sets IsValid to true and I can no longer perform the save.

How to replace a row in many to many relationship using generic services?

How would you go about replacing a row in many to many relationship using generic services? The current behavior allows me to add a new row whenever I am trying to change it using a DTO.

I want a behavior similar to the test TestChangeAuthorsNewListOk in Ch03_ManyToManyUpdate of your book using generic services.

Thank you.

Read many tracked?

We are using the GenericServices library for our project and it is very useful in almost every scenario! Except when update a list of objects. For example: a list of object with a bool property we want to toggle. In that case, a read many function that can read tracked database models would be handy. Is there any reason why you implemented this for the read single, but not the read many function?

Is it just time / effort? Or is there any technical reason why this would not work?

AutoMapper upgrade to version 9.0.0 results in error: MissingMethodException IgnoreAllPropertiesWithAnInaccessibleSetter()

We are trying to update all packages in our solution to the latest versions. When upgrading AutoMapper to version 9.0.0 to following error occurs:

System.MissingMethodException : Method not found: 'AutoMapper.IMappingExpression`2<System.__Canon,System.__Canon> AutoMapper.IMappingExpression`2.IgnoreAllPropertiesWithAnInaccessibleSetter()'.
   at GenericServices.Setup.Internal.CreateConfigGenerator.ConfigGenerator`2.AddSaveMappingToProfile(Profile writeProfile)
   at GenericServices.Setup.UnitTestSetup.AddSingleDto[TDto](SpecificUseData utData)
   at GenericServices.Setup.UnitTestSetup.SetupSingleDtoAndEntities[TDto](DbContext context, IGenericServicesConfig publicConfig)

The depedencies of GenericServices are:
AutoMapper (>= 7.0.1)
Microsoft.AspNetCore.JsonPatch (>= 2.1.1)
Microsoft.EntityFrameworkCore (>= 2.1.1)

It would be nice that GenericServices is compatible with the latest version of AutoMapper.

problem building RazorPageApp project.assets.json file not found

Hello! Just downloaded this library to check it out.

I downloaded the zip file, then double-clicked on the .sln file.

i'm using Visual Studio 2017 Community Edition, my dotnet.exe and sdk version is 2.1.300.

this is the build error i'm getting:
Severity Code Description Project File Line Suppression State
Error Assets file 'F:\jonsmith\EfCore.GenericServices-master\RazorPageApp\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. RazorPageApp C:\Program Files\dotnet\sdk\2.1.300\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets 198
i haven't encountered this problem before. any tip on a workaround?

doing a "dotnet restore" or "dotnet build" did not work.

wonder if i need to "update" project code to 2.1 ??

contents of RazorPageApp.csproj

netcoreapp2.0

SetupEntitiesDirect fails if one uses records in the entities

public class entity:
{
    [Key]
   public long Id { get; set; }
   public Reference Object { get; set; }
}
[Owned]
    public record Reference {

        public Reference(string type, Guid id)
        {
            Id = id;
            Type = type;
        }

        [StringLength(50)]  
        public string Type { get; set; }

        public Guid Id { get; set; }
    }

will fail with
The class Reference was not found in the TestCtx DbContext. The class must be either be an entity class derived from the GenericServiceDto/Async class.

Scanning DTOs in Assembly with Multiple Contexts Fails

I have a data model assembly that contains all of my entities and dtos. It has multiple contexts, for which some of my entities/dtos are associated to each.

I have multiple APIs each of which may only use one of several contexts.

When I call

services.ConfigureGenericServicesEntities(typeof(UserDbContext))
                .ScanAssemblesForDtos(Assembly.GetAssembly(typeof(CreateAPUserDto)))
                .RegisterGenericServices();

I get the following error because it is finding dto's for the other contexrt

Scanning redacted.DataModel>ChangeAuditEventStatusDto: The DTO/ViewModel class ChangeAuditEventStatusDto is linked to a entity class AuditEvent, but I couldn't find that class in the application's DbContext(s) you gave me to scan

Edit: If I add the other contexts to the ConfigureGenericServicesEntities method, I get past it, but then It fails with:

'You provided the a DbContext called AuditingDbContext, but it doesn't seem to be registered, or is a DbContext. Have you forgotten to register it?'

I don't want to have to register all of the db contexts in every API, just the ones I need for that API.

EF Core 5.0 Many-To-Many relationships not working

Hi,

At first look, there seem to be issues with the new ManyToMany relationship feature in EF Core 5.0. The relationship mapping table is represented as Dictionary<string, object> in EF causing GenericServices to fail during the registration of entity classes.

Is this known?

Order and Take are evaluating locally

I've an issue with a query that's using OrderBy and Take that's evaluating locally instead of in SQL. Probably I'm doing things wrong, hope you can help me out.

My Entities are:

public class Message 
    {
        public int Id { get; set; }
        public DateTime DateSent { get; set; }
        public string Subject { get; set; }
        public string Text { get; set; }
        public virtual IEnumerable<MessageUser> Users { get; set; }
    }

public class MessageUser 
    {
        [Key]
        public int Id { get; set; }
        public int MessageId { get; set; }
        public virtual Message Message { get; set; }
        public string UserId { get; set; }
        public virtual ApplicationUser User { get; set; }
        public DateTime? DateRead { get; set; }
        public DateTime? DateDeleted { get; set; }
    }

My DTOs look like this:

public class MessageUserDto : ILinkToEntity<Data.Models.MessageUser>
    {
        public int Id { get; set; }
        public virtual MessageDto Message { get; set; }
        public string UserId { get; set; }
        public DateTime? DateRead { get; set; }
        public DateTime? DateDeleted { get; set; }
    }

 public class MessageDto : ILinkToEntity<Data.Models.Message>
    {
        public DateTime DateSent { get; set; }
        public string Text { get; set; }
        public string Subject { get; set; }
        public IEnumerable<string> UserIds { get; set; }
    }

When I query via DbContext everything works fine:

var mesesages = _dbContext.MessageUser
                    .Where(m => m.UserId == user.Id && m.DateDeleted == null)
                    .OrderByDescending(m => m.Message.DateSent)
                    .Take(3)
                    .ToList();

This generates this SQL:

SELECT TOP(@__p_1) [m].[Id], [m].[CreatedBy], [m].[DateCreated], [m].[DateDeleted], [m].[DateRead], [m].[DateUpdated], [m].[MessageId], [m].[UpdatedBy], [m].[UserId]
FROM [MessageUser] AS [m]
INNER JOIN [Message] AS [m.Message] ON [m].[MessageId] = [m.Message].[Id]
WHERE ([m].[UserId] = @__user_Id_0) AND [m].[DateDeleted] IS NULL
ORDER BY [m.Message].[DateSent] DESC

But when I query via ICrudServices / DTOs the ORDER BY and TOP are not in the SQL:

var messages2  = _crudServices.ReadManyNoTracked<MessageUserDto>()
                    .Where(m => m.UserId == user.Id && m.DateDeleted == null)
                    .OrderByDescending(m => m.Message.DateSent)
                    .Take(3)
                    .ToList();

This generates this SQL:

SELECT [dtoMessageUser].[MessageId], [dtoMessageUser.Message].[DateSent], [dtoMessageUser.Message].[Subject], [dtoMessageUser.Message].[Text], [dtoMessageUser].[DateDeleted], [dtoMessageUser].[DateRead], [dtoMessageUser].[Id], CASE
    WHEN [dtoMessageUser].[MessageId] IS NULL
    THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END, [dtoMessageUser].[UserId]
FROM [MessageUser] AS [dtoMessageUser]
INNER JOIN [Message] AS [dtoMessageUser.Message] ON [dtoMessageUser].[MessageId] = [dtoMessageUser.Message].[Id]
WHERE ([dtoMessageUser].[UserId] = @__user_Id_0) AND [dtoMessageUser].[DateDeleted] IS NULL

This is the warning I get:

warn: Microsoft.EntityFrameworkCore.Query[20500]
      The LINQ expression 'orderby IIF((Property([dtoMessageUser], "MessageId") == null), null, new MessageDto() {DateSent = [dtoMessageUser.Message].DateSent, Subject = [dtoMessageUser.Message].Subject, Text = [dtoMessageUser.Message].Text}).DateSent desc' could not be translated and will be evaluated locally.
warn: Microsoft.EntityFrameworkCore.Query[20500]
      The LINQ expression 'Take(__p_1)' could not be translated and will be evaluated locally.

Querying on the entity itself via CrudServices does work:

var messages2  = _crudServices.ReadManyNoTracked<MessageUser>()
                    .Where(m => m.UserId == user.Id && m.DateDeleted == null)
                    .OrderByDescending(m => m.Message.DateSent)
                    .Take(3)
                    .ToList();

The class X has 0 primary keys. I can't handle that.

Hello Jon,

I have a Query Type included in my DbContext, and I run into this error, "The class X has 0 primary keys. I can't handle that." Is there a way to mark the query type so GenericServices skips it when initializing?

Update method matching on argument names and quantity

Case:
DDDEntity
{
string Prop1{get; private set}
string Prop2{get; private set}

public void SetProp1(string prop1){Prop1 = prop1;}
public void SetProp2(string prop2){Prop2 = prop2;}
public void SetProp1And2(string prop1, string prop2) {Prop1 = prop1; Prop2 = prop2;}
}

using CRUDService, call UpdateAndSave on a DTO that has both properties Prop1 and Prop2. It can't decide which method to use, as it sees all three as valid setters, even though only one of the methods can take both parameters.

Is it possible to force it to use the two-parameter method automatically (without having to specify "SetProp1And2" as a second argument? And would it be a reasonable thing to do?

The explicit error message I'm getting:
System.InvalidOperationException : There are multiple method, so you need to define which one you want used via the method parameter. The possible options are:
SetProp1And2(2 params)
SetProp2(1 params)
SetProp1(1 params)

I was hoping that it would automatically map to the SetProp1And2 method since it has both, without having to explicitly pass the name of the method.

Open GenereicServices solution in VS2015 results in error

Hello,

I have following problem with opening the solution in Visual Studio Pro 2015:
21-08-_2018_10-08-50

Somehow the csproj-files are different to the "normal" csproj-files. That's why VS refuses to open the projects of the solution:
21-08-_2018_10-14-49

So, how can I open the projects in VS?!

Kind regards

Conflict with AutoMapper v10.1.1, Throws Exception

Hello Jon,

I received the following stack trace when I have AutoMapper 10.1.1 installed (latest).

System.MissingMethodException: Method not found: '!!1 AutoMapper.IMapper.Map(!!0, !!1)'.
   at GenericServices.Internal.MappingCode.CreateMapper+GenericMapper<TDto, TEntity>.MapDtoToEntity(TDto dto, object entity)
   at CallSite.Target(Closure , CallSite , object , ProjectCreateDto , object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid3<T0, T1, T2>(CallSite site, T0 arg0, T1 arg1, T2 arg2)
   at GenericServices.Internal.MappingCode.EntityCreateHandler<TDto>.CreateEntityAndFillFromDto(TDto dto, string methodCtorName)
   at GenericServices.PublicButHidden.CrudServicesAsync<TContext>+<CreateAndSaveAsync>d__9<T>.MoveNext()

I think it would be at this line in your code.

Apparently there is a missing method in the new version of AutoMapper, it seems specifically in the Mapper class. Maybe somewhere around here??? Rolling back to 9.0.0 seemed to fix it. Is there anything that can be done in the library here to allow it to work with either version? Or is there something else I am missing that would allow us to use the latest AutoMapper?

Thank you for your insights,

Jeremy

SqlException: Violation of PRIMARY KEY constraint

Hello,
first of all thanks for your work on EfCore.GenericServices, we are using it.
Unfortunately there is a strange behavior when updating nested dto's
I created a project for you at
https://github.com/bleissem/TestNestedInDtosIssue13
and add a little description. So you should be able to reproduce the exception
SqlException: Violation of PRIMARY KEY constraint 'PK_AddressNotOwned'. Cannot insert duplicate key in object 'dbo.AddressNotOwned'.

To reproduce the error it should be enough to deploy the database from the Database1 project.
Then change the Connection string in the application.json and then run the WebApplication1 project.
In the ValuesController the code to examine is in the Get function

Regards
Alex B.

DTO with no properties causes null exception

Hi,

I have this code in https://gist.github.com/jenseralmeida/a51b55b2eb5d1e42a5ebd3e0ef2bd269 which is failing without a proper message explaining what I did wrong. The exception should clearly state that the DTO does not have any writable property, instead of a NullReferenceException later on the code where an instance value was expected.

I am pasting the failing method just below.

image

The first line of the DecodedDto.MatchCtorsAndStaticMethodsToProperties method is the one that could be changed to check and throws if there is no writable property.

image

I have put the full code for the unit-test copied from the gist above

using GenericServices;
using GenericServices.Setup;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace readonly_property
{
    public class UnitTest1
    {
        [Fact]
        public void Dto_with_no_writable_property_causes_null_exception()
        {
            using (var context = new SomeContext(InMemoryOptions.Create<SomeContext>()))
                context.SetupSingleDtoAndEntities<ImutableDto>();
        }

        private static class InMemoryOptions
        {
            public static DbContextOptions<T> Create<T>(string databaseName = "InMemory") where T: DbContext
                => new DbContextOptionsBuilder<T>()
                    .UseInMemoryDatabase(databaseName: databaseName)
                    .Options;
        }
    }

    public class SomeContext : DbContext
    {
        public SomeContext(DbContextOptions<SomeContext> options): base(options) { }
        public DbSet<ImutableEntity> Entities { get; set; }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<ImutableEntity>().Property(e => e.Id).ValueGeneratedNever();
            builder.Entity<ImutableEntity>().Property(e => e.Name);
        }
    }

    public class ImutableEntity
    {
        public ImutableEntity(int id, string name)
        {
            Id = id;
            Name = name;
        }
        public int Id { get; private set; }
        public string Name { get; private set; }
    }

    public class ImutableDto: ILinkToEntity<ImutableEntity>
    {
    }
}

Not sure if issue or working as intended

Per your Book example with the IncludeThen. When I do a genericService.ReadSingleAsync<Book>(id); am i to expect the list of Reviews to come with it as well? or is this only for the saving bit?

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.