locked
NET 5 Web API + EF Core HTTP POST - my approach not working RRS feed

  • Question

  • User379720387 posted

    Attempting to Post a new Transaction.  The Transaction entity has 9 related Entities, which in all can make up a single transaction record.  Not all related Entities are always used.

    Abbreviated Transaction looks like this:

        public partial class Transaction
        {
            public Transaction()
            {
                ClientRecords = new HashSet<ClientRecord>();
                ClientShoeTransactions = new HashSet<ClientShoeTransaction>();
                ProviderRxTransactions = new HashSet<ProviderRxTransaction>();
                ProviderSvcTransactions = new HashSet<ProviderSvcTransaction>();
                TodoRecords = new HashSet<TodoRecord>();
                TxnFiles = new HashSet<TxnFile>();
                TxnNotes = new HashSet<TxnNote>();
                TxnPictures = new HashSet<TxnPicture>();
            }
    
            [Key] public int TransactionId { get; set; }
            public int? ClientId { get; set; }
            public string Service { get; set; }
            public decimal? Charge { get; set; }
            public bool? IsBilled { get; set; }
            public int? BillNo { get; set; }
            public string Link { get; set; }
            public string TinyLink { get; set; }
            public bool? IsPaid { get; set; }
            public int? PropertyId { get; set; }
            public int? ProviderId { get; set; }
            public DateTime? Tdate { get; set; }
            public string ServiceDetails { get; set; }
            public int? OwnerLocationId { get; set; }

    bunches more here
    public virtual Client Client { get; set; } public virtual LocationTxRate Ltr { get; set; } public virtual Owner OwnerBillTo { get; set; } public virtual Owner OwnerLocation { get; set; } public virtual Property Property { get; set; } public virtual Provider Provider { get; set; } public virtual ServiceLevel Sl { get; set; } public virtual TxnVoiceRecord TxnVoiceRcrd { get; set; } public virtual ICollection<ClientRecord> ClientRecords { get; set; } public virtual ICollection<ClientShoeTransaction> ClientShoeTransactions { get; set; } public virtual ICollection<ProviderRxTransaction> ProviderRxTransactions { get; set; } public virtual ICollection<ProviderSvcTransaction> ProviderSvcTransactions { get; set; } public virtual ICollection<TodoRecord> TodoRecords { get; set; } public virtual ICollection<TxnFile> TxnFiles { get; set; } public virtual ICollection<TxnNote> TxnNotes { get; set; } public virtual ICollection<TxnPicture> TxnPictures { get; set; }

    Originally I set out to Post a new Transaction based on the Entity, and with your help I got as far as the Transaction object being serialized, however it resulted in response "404 bad request:

    string baseUrl = "https://localhost:5001";
    
            var httpClient = _clientFactory.CreateClient("ServerAPI");
            httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
    
            JsonSerializerOptions options = new()
            {
                ReferenceHandler = ReferenceHandler.Preserve,
                WriteIndented = true
            };
    
            var json = JsonSerializer.Serialize(txnNew, options);
            var y = JsonSerializer.Deserialize<Transaction>(json, options);
            var content = new StringContent(json);
    
            string errorMessage = null;
            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
            var response = await httpClient.PostAsync($"{baseUrl}/api/Txn/AddNew", content);
    
            var data = await response.Content.ReadAsStringAsync();
            Transaction result = new Transaction();
    
            if (response.IsSuccessStatusCode)

    Looking around SO and other places it was suggested not do this with the Entity because (not entirely sure), and I noticed that json was populated even while I did not put any data in.

    Created a new mopdel TxnNew that has the stuff I need for a new Transaction and instantiated it like so:

    TxnNew txnNew = new TxnNew();

    Once I start populating ProviderSvcTransation I run into a problem: " System.NullReferenceException: Object reference not set to an instance of an object." on the red highlighted line.

    ProviderSvcTransaction newPst = new ProviderSvcTransaction();
            newPst.BilledRate = primaryCharges;
            newPst.TransactionId = txnNew.TransactionId;
            newPst.ProviderServiceId = selectedService;
            txnNew.ProviderSvcTransactions.Add(newPst);

    primaryCharges and selectedService have the correct values, then it must be that I cannot do txnNew.TransactionId like I could when txnNew was a new instance of the Transaction entity.

    What is the proper technique here?

    Friday, April 9, 2021 9:21 PM

All replies

  • User1120430333 posted
    TxnNew txnNew = new TxnNew();

    Just becuase you instanced the object above, it doesn't mean that any object within the instanced object is instanced as well, which would result in a null valued object within the instanced object if the object was not instanced.

    The code is just an example of an object being instanced inside an object that has been instanced 

    using System.Collections.Generic;
    
    namespace Entities
    {
        public class DtoCache
        {
            public List<DtoProjectType> ProjectTypes { get; set; } = new List<DtoProjectType>();
            public List<DtoStatus> Statuses { get; set; } = new List<DtoStatus>();
            public List<DtoResource> Resources { get; set; } = new List<DtoResource>();
            public List<DtoDuration> Durations { get; set; } = new List<DtoDuration>();
        }
    }
    

    You can either do the above as far as instancing a new object within the container object you have instanced.

    var dtocache = new DtoCache(). //every object the List<T>(s) are instanced within DtoCache when DtoCache is instanced.

    Or if none of the List<T>(s) were instanced in DtoCache, then you would have to instance them manually.

    var dtocache = new DtoCache()

     dtocache.ProjectTypes = new List<DtoProjectType>();   

    Or you could have used a private backing object and instanced it when DtoCache was instanced.

    public class DtoCache
     {
            private  List<DtoProjectType> projecttypes = new List<DtoProjectType>();
    
            public List<DtoProjectType> ProjectTypes
            {
                get { return projecttypes; }   
                set { projecttypes = value; } 
             }
    }
    
    
    

    As far as the 404 - not found, it means that the HTTPclient code trying to access a WebAPI controller method, the WebAPI controller method could not be found, which is usually due to a malformed URL.

    Saturday, April 10, 2021 1:49 AM
  • User379720387 posted

    Then how do I add members to txnNew.ProviderSvcTransactions?

    //new primary service
            txnNew.ProviderSvcTransactions = new List<ProviderSvcTransaction>();
            ?.BilledRate = primaryCharges;
            ?.TransactionId = txnNew.TransactionId;
            ?.ProviderServiceId = selectedService;

    Saturday, April 10, 2021 6:47 PM
  • User-474980206 posted

    create an object, set its properties and add to the list:

    txnNew.ProviderSvcTransactions = new List<ProviderSvcTransaction>();
    
    txnNew.ProviderSvcTransactions.Add(new ProviderSvcTransaction
    {
            BilledRate = primaryCharges,
            TransactionId = txnNew.TransactionId,
            ProviderServiceId = selectedService
    });

    Saturday, April 10, 2021 8:19 PM
  • User1120430333 posted
    txnNew.ProviderSvcTransactions = new List<ProviderSvcTransaction> 
    {
    new ProviderSvcTransaction
    {
    BilledRate = primaryCharges, TransactionId = txnNew.TransactionId, ProviderServiceId = selectedService }
    }; You can do it like above too and just continue to add items to the collection without having to do the List.Add().
    Saturday, April 10, 2021 9:19 PM
  • User379720387 posted

    Code is hitting my controller now.

    For that to happen I had to remove the JsonSerializerOptions.

    Thanks!

    Not so fast! Yes, code is hitting my controller and it is even updating the database, everthing works just fine, but.....

    Back in the callee (my Razor Server page) I have 500 internal server error:

    system.Json.Text exception and the  possible circular object

    For which I have the json serialize option with ReferenceHandler.Preserve right?

    Oops, now there is a bad request 400

    Then no records are written to the db

    this reveals the following error message: 

    {"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-e99e67c4d5da7645b6a94b0bca34affd-5e76999d6926874b-00","errors":{"$.PstsNew":["The JSON value could not be converted to System.Collections.Generic.List`1[BtApiEf5.Model.Custom.PstNew]. Path: $.PstsNew | LineNumber: 30 | BytePositionInLine: 14."]}}
    "PstsNew": {
        "$id": "2",
        "$values": [
          {
            "$id": "3",
            "Id": 0,
            "ProviderServiceId": 11,
            "TransactionId": 0,
            "BilledRate": 90.0000
          },
          {
            "$id": "4",
            "Id": 0,
            "ProviderServiceId": 4306,
            "TransactionId": 0,
            "BilledRate": 8.0000
          }
        ]
      },

    I suspect it is complaining about the highlighted fragments that are a result of ReferenceHandler.Preserve.

    For now I will unmark as answer, later to be restored to answer status, once I can POST without exceptions popping up.

    Saturday, April 10, 2021 11:20 PM
  • User1120430333 posted

    Your code on the WebApi client=side code blew up because the List<T> you were trying  to populate was not instanced into a object within the txn container object. That was fixed. because you state you were  able to reach the WebApi controller  method. Your new problem has nothing to do with the fix of the object not set to an instance of an object on the client-side code.

    Myself, I don't see how you think that txn on a post doing a Savechanges(txn) is going to work. IMHO, it's  not going to work, just saying.

    Tuesday, April 13, 2021 1:15 PM
  • User379720387 posted

    I realize that "approach" is sort of a moving target at the moment. Will mark those back to answered later.

    "approach not working" = saving a Transaction and related Entities without any exceptions.

    I don't know how this is supposed to work, just copied the code from what I did in my old EF6.4 app.

    What is the right way of doing this then?

    Tuesday, April 13, 2021 1:42 PM
  • User475983607 posted

    Circular reference errors are usually due to returning an Entity that has navigation properties.  The serializer's job is to serialize all the public properties.  Well, if the Entity has navigation properties then the serializer fills the navigation property.  If those navigation properties also have navigation properties, the serializes gets those navigation records too.  And, you get the standard circular reference exception.  Otherwise, you could deserialize the entire DB.  Anyway, we have discussed this in your other posts.

    The 400 error means the request was not correct.  If you read the error message, it looks like the Action is expecting a List<BtApiEf5.Model.Custom.PstNew> but the JSON you shared above is a single type; BtApiEf5.Model.Custom.PstNew. I'm guessing you can just fix the Action method's input parameter so it accepts a BtApiEf5.Model.Custom.PstNew rather than a List<BtApiEf5.Model.Custom.PstNew>.

    Tuesday, April 13, 2021 2:37 PM
  • User379720387 posted

    @mgebhard  the json I shared up above is a subset of the whole thing.

    {
      "$id": "1",
      "TransactionId": 0,
      "ProviderId": 22,
      "ClientId": 49,
      "OwnerLocationId": 61,
      "CountryId": 1,
      "aId": null,
      "slId": 1,
      "isExported": false,
      "Service": null,
      "Charge": 152.0000,
      "isCharge": true,
      "PayAmount": 0,
      "TDate": "2021-04-13T15:24:26.7589067Z",
      "BDate": null,
      "PDate": null,
      "BillNo": 0,
      "IsBilled": false,
      "IsPaid": null,
      "isProForma": false,
      "SvcLevel": null,
      "isAdjusted": false,
      "adjAddOn": 0,
      "TaxRate": 0,
      "Tax": 0,
      "isTaxable": true,
      "ccAddOn": 0,
      "CurrencySymbol": null,
      "PstsNew": {
        "$id": "2",
        "$values": [
          {
            "$id": "3",
            "Id": 0,
            "ProviderServiceId": 11,
            "TransactionId": 0,
            "BilledRate": 90.0000
          },
          {
            "$id": "4",
            "Id": 0,
            "ProviderServiceId": 4312,
            "TransactionId": 0,
            "BilledRate": 40.0000
          },
          {
            "$id": "5",
            "Id": 0,
            "ProviderServiceId": 4309,
            "TransactionId": 0,
            "BilledRate": 22.0000
          }
        ]
      }
    [Route("AddNew")]
            [HttpPost]
            public async Task<ActionResult<TxnNew>>PostTxn(TxnNew newTxn)
            {
                try
                {
                    Transaction txn = new Transaction();
                    txn.ProviderId = newTxn.ProviderId;
                    txn.ClientId = newTxn.ClientId;
                    txn.OwnerLocationId = newTxn.OwnerLocationId;
                    txn.AId = newTxn.aId;
                    txn.SlId = newTxn.slId;
                    txn.IsExported = newTxn.isExported;
                    txn.Service = newTxn.Service;
                    txn.Charge = newTxn.Charge;
                    txn.IsCharge = newTxn.isCharge;
                    txn.PayAmount = newTxn.PayAmount;
                    txn.Tdate = newTxn.TDate;
                    txn.Bdate = newTxn.BDate;
                    txn.Pdate = newTxn.PDate;
                    txn.BillNo = newTxn.BillNo;
     
                    txn.IsBilled = newTxn.IsBilled;
                    txn.IsPaid = newTxn.IsPaid;
                    txn.IsProForma = false;
                    txn.IsAdjusted = newTxn.isAdjusted;
                    txn.AdjAddOn = newTxn.adjAddOn;
                    txn.TaxRate = newTxn.TaxRate;
                    txn.Tax = newTxn.Tax;
                    txn.IsTaxable = newTxn.isTaxable;
                    txn.CcAddOn = newTxn.ccAddOn;
     
                    txn.ProviderSvcTransactions = new List<ProviderSvcTransaction>();
                    if (newTxn.PstsNew.Count > 0)
                    {
                        foreach (var item in newTxn.PstsNew)
                        {
                            txn.ProviderSvcTransactions.Add(new ProviderSvcTransaction
                            {
                                BilledRate = item.BilledRate,
                                TransactionId = item.TransactionId,
                                ProviderServiceId = item.ProviderServiceId
                            });
                        }
                    }
                   
                    
                    context.Add(txn);
                    await context.SaveChangesAsync();
                    return Ok(txn);
                }
                catch (Exception e)
                {
                    return BadRequest(e.Message);
                }
            }

    @DA924 seems to think there is something wrong with SaveChanges

    I only have (1) one POST action accepting BtApiEf5.Model.Custom.TxnNew model which contains PstNew (and a more).

    Tuesday, April 13, 2021 3:37 PM
  • User475983607 posted

    @mgebhard  the json I shared up above is a subset of the whole thing.

    The error message states, The JSON value could not be converted to System.Collections.Generic.List`1[BtApiEf5.Model.Custom.PstNew].  Your code is trying to convert JSON into a List<PstNew>. 

    The JOSN shows a "PstsNew" property but PstsNew is a single item defined with squirrely brackets {}, not an array with square brackets [].  The "values" property within PstsNew is an array though. 

    Essentially, you have a mismatch between the JSON data format and the input parameter type "TxnNew".  You have not shared the TxnNew model but I'm guessing somewhere there is a collection property of type ICollection<PstNew> that should be PstNew.

    Tuesday, April 13, 2021 4:22 PM
  • User379720387 posted

    public class TxnNew
        {
            public int TransactionId { get; set; }
            public int ProviderId { get; set; }
    
            public int ClientId { get; set; }
            public int OwnerLocationId { get; set; }
            public int CountryId { get; set; }
    
            public int? aId { get; set; }
    
            public int slId { get; set; }
            public bool isExported { get; set; }
            public string Service { get; set; }
            public decimal Charge { get; set; }
            public bool isCharge { get; set; }
            public decimal PayAmount { get; set; }
            public DateTime? TDate { get; set; }
            public DateTime? BDate { get; set; }
            public DateTime? PDate { get; set; }
            public int BillNo { get; set; }
            public bool IsBilled { get; set; }
            public bool? IsPaid { get; set; }
    
            public bool? isProForma { get; set; }
            public string SvcLevel { get; set; }
            public bool isAdjusted { get; set; }
            public decimal adjAddOn { get; set; }
            public decimal TaxRate { get; set; }
            public decimal Tax { get; set; }
            public bool isTaxable { get; set; }
            public decimal ccAddOn { get; set; }
            public string CurrencySymbol { get; set; }
            
            public List<PstNew> PstsNew { get; set; }
    
        }

    Tuesday, April 13, 2021 5:26 PM
  • User475983607 posted

    Well, right there in the Txnnew class the Pstsnew property is defined as a collection. 

    public List<PstNew> PstsNew { get; set; }

    Again, the JSON type is NOT a collection it is a single type as defined by the squirrely brackets.  This is a matter of the round peg goes in the round hole.  Either fix the client code to pass a List<PstNew> or fix the model property definition.

    Tuesday, April 13, 2021 6:12 PM
  • User379720387 posted

    After reading up on things on SO I have seen the suggestion to use ICollection, and not List.

    TnxNew.cs now has:

    public ICollection<PstNew> PstsNew { get; set; }
    JsonSerializerOptions options = new()
            {
                ReferenceHandler = ReferenceHandler.Preserve,
                WriteIndented = true
            };
    
    var json = JsonSerializer.Serialize(txnNew); //, options

    If I make the POST without ", options" i.e. straight up System.Text.Json, then I have square brackets like below but I get the JsonException about object cycle detected

    "isTaxable": true,
      "ccAddOn": 0,
      "CurrencySymbol": null,
      "PstsNew": [
        {
          "Id": 0,
          "ProviderServiceId": 11,
          "TransactionId": 0,
          "BilledRate": 90
        },
        {
          "Id": 0,
          "ProviderServiceId": 4312,
          "TransactionId": 0,
          "BilledRate": 40
        },

    With the ReferenceHandler set to Preserve I have curly brackets and a bad request exception

    "isTaxable": true,
      "ccAddOn": 0,
      "CurrencySymbol": null,
      "PstsNew": {
        "$id": "2",
        "$values": [
          {
            "$id": "3",
            "Id": 0,
            "ProviderServiceId": 11,
            "TransactionId": 0,
            "BilledRate": 90.0000
          },
          {
            "$id": "4",
            "Id": 0,
            "ProviderServiceId": 4312,
            "TransactionId": 0,
            "BilledRate": 40.0000
          },

    Is the ICollection property now properly defined?

    Tuesday, April 13, 2021 7:59 PM
  • User1120430333 posted

    It made no difference if you used ICollection or List<T>, both involve a collection.

    IEnumerable and ICollection in C# (c-sharpcorner.com)

    IMHO, you are trying to mimic EF persistence model with this model you have created. It seems that you are trying to use this mimic model and expecting it's going to be persisted by EF? If that's what you are trying to do, then I can see problems. 

    Tuesday, April 13, 2021 8:34 PM
  • User475983607 posted

    Is the ICollection property now properly defined?

    No.  You are struggling to understand the difference between an array of types...

    int[] mynumbers = int[] {1,2,3,4};

    ...and a single type.  

    int mynumber = 1;

    In JSON an array has the following syntax

    [1,2,3,4]

    An array of types in JSON has the following format.

    [ {"mynum": 1}, {"mynum": 2}, {"mynum": 3}, {"mynum": 4} ]

    Anyway, List<T> and ICollection<T> are both collections types.  Your client is not sending a collection to the action.  The client is sending a single type of PstsNew.

    Fix the model if you do not want to fix the client.

    public class TxnNew
    {
           //Other properties 
            public PstNew PstsNew { get; set; }
    }

    Tuesday, April 13, 2021 9:44 PM
  • User379720387 posted

    I understand the concept, just don't understand how to affect the necessary changes.

    Making the change in TxnNew has all sorts of consequences for the controller code. I have reconsidered and I want to know how to make the changes in the razor code:

     async Task AddTxn()
        {
            string ServiceSummary = "";
    
            TxnNew txnNew = new TxnNew();  //single Txn ==> OK
    
            txnNew.ProviderId = theProvider.ProviderId;
            txnNew.ClientId = theClient.ClientId;
            txnNew.slId = theClient.SvcLevel;

    Then  PstNew:

            //new primary service
            txnNew.PstsNew = new List<PstNew>();
    
            txnNew.PstsNew.Add(new PstNew
            {
                BilledRate = primaryCharges,
                TransactionId = txnNew.TransactionId,
                ProviderServiceId = selectedService
            });
    
            //add ons
            if (addOnOptions.Count > 0)
            {
                foreach (var item in selectedAddOns)
                {
                    txnNew.PstsNew.Add(new PstNew
                    {
                        BilledRate = item.BillingRate,
                        TransactionId = txnNew.TransactionId,
                        ProviderServiceId = item.RateId
                    });
                }
            }

    Thought I now have one Txn plus n Psts, what am I doing wrong here?

    Tuesday, April 13, 2021 11:18 PM
  • User379720387 posted

    You are over estimating my capabilities here.

    I am trying to create one new Txn and all the related Entities in the db.

    Taken my EF6.4 code and started with it, thinking it should be very close.

    Don't know how it needs to be done with EF Core and all the Models

    Tuesday, April 13, 2021 11:25 PM
  • User1120430333 posted

    You may not be implementing any SoC in the WebAPI solution where the WebAPI is calling a data access layer and using objects in the DAL to do CRUD with the database from what I gather.

    Separation of concerns - Wikipedia

    Architectural principles | Microsoft Docs

    IMO,  you could have made a DAL classlib project, used a functional test project, like a console project,   to use the DAL and just loaded the 'test' txn with data and tried to persist the data using EF to expose any issues before you tried to use it with the WebAPI. By the time you were done doing the functional tests, you would have gotten all of the kinks out and known that what you were doing was solid.

    To me, the WebAPI is just passthrough logic, and it is calling upon the DAL as an example to do CRUD with the database. It's easy to test the DAL than trying to test some post action where you have the CRUD logic in a WebAPI controller method.

    IMO, this should have been the first thing that should have been done where you tested the CRUD operations using EF in some manner as I have depicted, then you work your way up the stack.

    The second thing I have issues with is the 'txn' model you have created. I recall seeing attributes over properties as if you were looking at it as some kind of EF persistence model. I thought I saw you trying to persist the 'txn' as/is to the database using EF. 

    The 'txn' model should be a vanilla model void of any attributes or properties dealing with any navigation, a complete disconnect from EF. The txn is a simple object with simple objects within it to carry data. That's it that's the "txn's" job is to carry data. The 'txn' should not be used for anything else but to be a simple container object carrying simple objects that match the EF model for carrying data similar to the DTO.  

    Wednesday, April 14, 2021 7:54 AM
  • User475983607 posted

    Don't know how it needs to be done with EF Core and all the Models

    This issue has nothing to do with EF Core.   You would have the same problem if you tried do the same with a standard method because the calling arguments do not match the method parameters.

    The easiest thing to do is fill the TxtNew object on the client.  You can create two copies of the types, one in the Blazor app and one in the API.  Or you can share the type between projects the using a library.  

    Wednesday, April 14, 2021 11:06 AM
  • User379720387 posted

    Attempting a minimally viable solution. A DAL layer is an enormous complication for me at this point. Making the app maintainable is last after working, and some others.

    The Entitty Models are: Transaction, ProviderSvcTransaction (1 to many) and a few more.

    Created custom Models: TxnNew, PstNew, and a few more. These have no knowledge of EF, it is a container object

    @mgebhard has suggested the types do not match, this is still a work in progress and I have not yet found the right combination.

    In the controller I am creating a new Transaction and more than 1 ProviderSvcTransactions and do the mapping from TxnNew.

    Either I live with the 500 error about object cycle and have data in my db, or figure out a way to make ReferenceHandler.Preserve deliver the needed [] and get rid of the 400 bad request

    Wednesday, April 14, 2021 9:06 PM
  • User1120430333 posted

    Attempting a minimally viable solution. A DAL layer is an enormous complication for me at this point. Making the app maintainable is last after working, and some others.

    Maintainability is part of using SoC, but the other part is testability as well with using SoC, becuase if you had followed SoC principles, you would have been able to test the DAL CRUD operations using EF with this 'txn' model doing testing of the DAL independently from the WebAPI using a test harness. You could have unit tested and functional tested the DAL,  and you would have ironed out all the kinks without the WebAPI being involved.  The final results would be the WebAPI would be just dumb passthrough logic the conduit between the client-side and the DAL sitting behind the WebAPI service.

    As it stands now, you seem to be going through unnecessary hell. :)

    @mgebhard has suggested the types do not match, this is still a work in progress and I have not yet found the right combination.

    How can they not match? Not match what? If the classes are put into one area shared by the client and the service, like a shared classlib project that contains the classes, surely they would match between the client and service side usages, if that's what you are talking about they don't match. The 'txn' should be a simple Json serialization or deserialization on the client-side and used as a parm on the HTTPClient() post to a WebAPI action method. 

    Either I live with the 500 error about object cycle and have data in my db, or figure out a way to make ReferenceHandler.Preserve deliver the needed [] and get rid of the 400 bad request

    Do you know who or what is throwing the exception, becuase a .NET exception is being thrown? And are you seeing the exception.message including the inner.exception.message if it's available?

    Thursday, April 15, 2021 8:03 AM
  • User379720387 posted

    My original code:

    context.Add(txn); 
    await context.SaveChangesAsync();
    return Ok (txn);

    changed to

    context.Add(txn); 
    await context.SaveChangesAsync();
    return Ok (txn.TransactionId);

    This works!

    Now, why this would throw a JsonException object cycle detected is beyond me.

    P.S. This exception pops up using System.Text.Json and Newtonsoft.Jsom

    Saturday, April 17, 2021 2:52 AM
  • User1120430333 posted

    If this is for inserting of the txn's objects to the database, then why is it that you need to be returning anything back to the client-side for what? As far as I look at the code you have,  the only thing if anything that should be coming back is HTTP 200 and that's it. And for me, I wouldn't even send the HTTP 200 back. Either the Save() saved everything successfully, or it blew-up sending back the HTTP 500 on an exception and the reason why it blew-up. 

    System.Text.Json.JsonException: A possible object cycle was detected which is not supported (makolyte.com)

    If the above is the exception you are getting back, I could see why you are getting the exception you are getting according to the article I read about the exception,  and the way you are doing the Save() txn and the return of the saved txn that still had some kind of Json serialization refernce through the whole save and return of objects. It could possibly be a object cycle on the foreign-key properties that had no ID of the parent object assigned, and during EF save process,  the foreign-key properties were assigned that triggered a object cycle on child objects causing the Json serialization to throw the exception.

    Why are you even trying to send the txn back or even the ID?  Also, I don't particularly see the saving of the txn as optimal either, if the txn as/is is sent from the client-side over to the WebAPI,  and it is saved as/is instead of mapping the txn objects to the EF persistence model objects, and the EF model objects are persisted. If this is what you are doing of just persisting the txn as/is, then it would never make passed a code review IMHO.

    If you got to send the objects back, then do a read and rebuild a new txn to break references.

    But what you have going on is not an optimal approach IMHO, particularly if you have to send objects back to the client on inserting of objects to the database.

    Saturday, April 17, 2021 10:25 AM
  • User475983607 posted

    Now, why this would throw a JsonException object cycle detected is beyond me.

    P.S. This exception pops up using System.Text.Json and Newtonsoft.Jsom

    I explained why this error happens above.   You can configure the serializer in the startup.cs file to not throw this error.  

    https://stackoverflow.com/questions/12584986/how-to-fix-circular-reference-error-when-dealing-with-json

    As explained many time in this and your other threads, do not return entities form Web API.  Clients should have no idea Web API is using Entity Framework.

    Saturday, April 17, 2021 10:31 AM