Circular reference when serializing an object: Cannot set culprit properties to null - c#

I have a circular reference exception when serializing a c# model object (I'm using Entity Framework, and the object is the model passed to the view that comes from a LINQ query).
(It does not matter whether I use System.Web.Script.Serialization.JavaScriptSerializer() or Newtonsoft.Json.JsonConvert.SerializeObject(Model)).
I found which properties are causing the circular reference and come to the conclusion that setting them to null (as I don't need them in the view) could be a solution, but for any reason I cannot set them to null successfully.
#section scripts{
#Html.Partial("_GoogleMap", "")
#Scripts.Render("~/bundles/google_maps_plugins")
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
<script type="text/javascript" defer>
$(function () {
my_app.finca.InicializarFormulario(#Html.Raw(serializer.Serialize(Model)));
});
</script>
}
...
var q =
from
finca in db.finca
join consecion in db.concesion
on finca.concesion_id equals consecion.concesion_id
join oficina in db.oficina
on finca.oficina_mas_cercana_id equals oficina.oficina_id
join planificacion in db.planificacion
on finca.planificacion_defecto_id equals planificacion.planificacion_id
join ruta in db.ruta
on finca.ruta_defecto_id equals ruta.ruta_id
join acometida in db.acometida
on finca.acometida_defecto_id equals acometida.acometida_id
where
finca.finca_id == finca_id
select new FincaViewModel
{
descripcion = finca.descripcion,
finca_id = finca.finca_id,
oficina_mas_cercana_id = finca.oficina_mas_cercana_id,
oficina = finca.oficina.nombre,
planificacion_id = finca.planificacion_defecto_id,
planificacion = finca.planificacion.nombre_planificacion,
ruta_id = finca.ruta_defecto_id,
ruta = ruta.descripcion,
acometida_id = finca.acometida_defecto_id,
acometida = acometida.nombre,
direccion_id = finca.direccion_id,
codigo_gis = finca.codigo_gis,
concesion_id = finca.concesion_id,
concesion = finca.concesion.nombre_concesion,
disponible_contratacion_bit = finca.disponible_contratacion,
disponible_contratacion = finca.disponible_contratacion ? General.Si : General.No,
numero_alternativo = finca.numero_alternativo,
fecha_ultima_inspeccion = finca.fecha_ultima_inspeccion,
latitud = finca.latitud,
longitud = finca.longitud,
ps = finca.ps,
orden_trabajo = finca.orden_trabajo
};
var f = await q.FirstOrDefaultAsync();
f.ps = RemoveCircularReference(f.ps);
...
private ICollection<ps> RemoveCircularReference(ICollection<ps> ps) {
ps.ToList().ForEach(p => p.contrato.ToList().ForEach(c =>
{
c.cliente.tipo_cliente.cliente = null;
c.cliente.cliente_historial = null;
}));
return ps;
}
The fact is that after RemoveCircularReference the properties are still not null, which is nonsense. Weirdly, if I set a breakpoint after the method and launch it again the properties are indeed set to null, but not the first time :s
Edit 1: Adding "finca" class.
public class FincaViewModel
{
public int finca_id { get; set; }
public string descripcion { get; set; }
public DateTime fecha_creacion { get; set; }
public string disponible_contratacion { get; set; }
public bool disponible_contratacion_bit { get; set; }
public int concesion_id { get; set; }
public string concesion { get; set; }
public string codigo_gis { get; set; }
public string numero_alternativo { get; set; }
public DateTime? fecha_ultima_inspeccion { get; set; }
public decimal? latitud { get; set; }
public decimal? longitud { get; set; }
public int oficina_mas_cercana_id { get; set; }
public int direccion_id { get; set; }
public string direccion { get; set; }
public short ambito_id { get; set; }
public string ambito { get; set; }
public ICollection<ps> ps { get; set; }
public ICollection<PSFincaViewModel> psvm { get; set; }
public int pscount { get; set; }
public int ctcount { get; set; }
public string oficina { get; set; }
public int planificacion_id { get; set; }
public int planificacion_defecto_id { get; set; }
public string planificacion { get; set; }
public int ruta_id { get; set; }
public int ruta_defecto_id { get; set; }
public string ruta { get; set; }
public ICollection<orden_trabajo> orden_trabajo { get; set; }
public ICollection<FichaOtViewModel> orden_trabajo_vm { get; set; }
public string orden_trabajo_tipo { get; set; }
public int acometida_id { get; set; }
public int acometida_defecto_id { get; set; }
public string acometida { get; set; }
public ICollection<AcometidaViewModel> acometidas { get; set; }
}
Edit 2: Lazy loading.
I don't know if it may have something to do but I have:
db.Configuration.LazyLoadingEnabled = false;
I've tried to set it to true just to test, but in that case f.ps.contrato is not fetch.
Edit 3: Lazy loading (part 2).
I'm quite sure the issue has to do with lazy, but I don't know yet how to limit recursion in serializer.
Edit 4: App ends unexpectedly when trying the link suggested by #GSerg.
The link seems to be useful, but when using: var settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }; my app just "gets crazy" and ends unexpectedly
Edit 5: New attempt with JsonConvert.
I've also tried with JsonConvert using
var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
to no avail (the same result), the app ends unexpectedly, what makes me suspect I'm running into memory problems or whatever. Well, I'm stuck, and I'll have to change direction in finding a solution.

After many different unsuccessful attempts on avoiding/skipping the circular reference when serializing I decided to change the direction on finding a solution.
The problem regarding not being able to set null to the culprit properties was the lazy loading, but disabling it would prevent "contratos" in "ps" to be fetched.
What I ended doing is to disable lazy loading and loading the properties in question manually (instead of the Entity Framework auto asigning).
db.Configuration.LazyLoadingEnabled = false;
var q =
from
finca in db.finca
join ...
where ...
select new FincaViewModel
{
...
ps = finca.ps,
orden_trabajo = finca.orden_trabajo
};
var f = await q.FirstOrDefaultAsync();
f.acometidas = acometidasFinca;
f.psvm = ObtenerPS(f.ps);
f.orden_trabajo_vm = await ObtenerOT(f.orden_trabajo);
f.ps = ContratosPorPs(f.ps);
return f;
And
public ICollection<ps> ContratosPorPs(ICollection<ps> ps)
{
foreach (var p in ps)
{
p.contrato = new ContratoOperations().ContratosPorPs(p.ps_id);
}
return ps;
}
Now I can serialize with no circular reference exception.

Related

Remembering the Syntax for adding a list inside a list

there I have done this before but forgotten the syntax. I am making a JSON file which looks list the classes below but on the submit button I want to add them but I can't remember the syntax for when there is a list inside a list if that makes sense! my classes are:
public class LineItemCheck
{
public string Id { get; set; }
public string Check { get; set; }
public bool Yes { get; set; }
public bool No { get; set; }
}
public class LineItem
{
public string Category { get; set; }
public List<LineItemCheck> LineItemChecks { get; set; }
}
public class RootObject
{
public object SerialNumber { get; set; }
public object UnitNumber { get; set; }
public object Refrigerant { get; set; }
public object ModelNumber { get; set; }
public object BeltSize { get; set; }
public object FreezingUnitComments { get; set; }
public object VisualInspectionComments { get; set; }
public List<LineItem> LineItems { get; set; }
}
private void SubmitButton_Clicked(object sender, EventArgs e)
{
var checklist = new RootObject();
var checklistLineItem = new List<LineItem>();
var checklistLineItemChecks = new List<LineItemCheck>();
checklist.SerialNumber = SerialNumber.Text.ToString();
checklist.UnitNumber = UnitNumber.Text.ToString();
checklist.Refrigerant = Refrigerant.Text.ToString();
checklist.ModelNumber = ModelNumber.Text.ToString();
checklist.BeltSize = BeltSize.Text.ToString();
checklistLineItem.Add(new LineItem() {
Category = "Ziegra Machines Only",
LineItemChecks = new LineItemCheck(LineItemCheck.
),
});
as you can see the bottom section is wrong which is what i am trying to solve thanks
UPDATE: I remember the syntax myself sorry for posting but the answer i was looking for was:
var checklist = new RootObject();
var checklistLineItem = new List<LineItem>();
var checklistLineItemChecks = new List<LineItemCheck>();
checklist.SerialNumber = SerialNumber.Text.ToString();
checklist.UnitNumber = UnitNumber.Text.ToString();
checklist.Refrigerant = Refrigerant.Text.ToString();
checklist.ModelNumber = ModelNumber.Text.ToString();
checklist.BeltSize = BeltSize.Text.ToString();
checklistLineItem.Add(new LineItem() {
Category = "Ziegra Machines Only",
LineItemChecks = new List<LineItemCheck>()
{
new LineItemCheck()
{
Check = "",
},
new LineItemCheck()
{
Check = ""
}
}
});
If I understand your question correctly the following should be the right syntax:
checklistLineItem.Add(new LineItem()
{
Category = "Ziegra Machines Only",
LineItemChecks = new List<LineItemCheck>()
{
new LineItemCheck(),
new LineItemCheck(), ...
},
});
If I may: I would amend your models like so.
public class LineItem
{
public string Category { get; set; }
public List<LineItemCheck> LineItemChecks { get; private set; }
public LineItem()
{
LineItemChecks = new List<LineItemCheck>();
}
}
public class RootObject
{
public object SerialNumber { get; set; }
public object UnitNumber { get; set; }
public object Refrigerant { get; set; }
public object ModelNumber { get; set; }
public object BeltSize { get; set; }
public object FreezingUnitComments { get; set; }
public object VisualInspectionComments { get; set; }
public List<LineItem> LineItems { get; private set; }
public RootObject()
{
LineItems = new List<LineItem>();
}
}
That is, initializing all the collections in a constructor. Properties that expose collections and that can be null are ugly and, more importantly, unexpected. I never expect a collection to be null if it's exposed through a property, I expect it to just be empty, and that I can add stuff to it right away. I'm not in charge of a collection exposed by your object, it's your object's business to initialize itself properly.
This is the consensus in .NET land, you will be hard-pressed to find an example of a collection exposed by a library that is null upon instantiation of the object.
Also notice that I made the setters private, because no other object should be able to flat-out replace a collection exposed by a different object.
Then adding items to collections is simply a matter of:
var root = new RootObject();
var lineItem = new LineItem();
lineItem.LineItemChecks.Add(new LineItemCheck() { /* init properties here */ });
root.LineItems.Add(lineItem);

EF Core return specific columns from child tables

I don't want to use .include to get the entire child tables. I just need select columns.
public class ProjectTypeDTO {
public string Type { get; set; }
}
public class CourseDTO {
public string CourseCode { get; set; }
public string CourseTitle { get; set; }
}
public class ProjectDTO {
public int Id { get; set; }
public ProjectTypeDTO ProjectType { get; set; }
public CourseDTO Course { get; set; }
public string StartTerm { get; set; }
public DateTime SignOff { get; set; }
public DateTime StartDateTime { get; set; }
}
[HttpGet("getallprojects")]
public IActionResult GetAllProjects()
{
var projects = _context.Projects
.Select(p => new ProjectDTO
{
Id = p.Id,
ProjectType = { Type = p.ProjectType.Type },
Course = { CourseCode = p.Course.CourseCode, CourseTitle = p.Course.CourseTitle },
StartTerm = p.StartTerm,
SignOff = p.SignOff,
StartDateTime = p.StartDateTime,
}).ToList();
return Ok(projects);
}
This is throwing a "NotImplementedException: The method or operation is not implemented." error.
I've tested it as an anonymous function and it works.
var projects = _context.Projects
.Select(p => new
{
p.Id,
p.ProjectType.Type,
p.SignOff,
p.StartDateTime,
p.Course.CourseCode,
p.Course.CourseTitle,
p.StartTerm
}).ToList();
An anonymous type won't work for my app, since I need to be able to make changes to this data before it gets returned.
Based on other examples I've seen here and other sites, this looks correct. Could it be a bug?
I haven't seen that syntax for sub-objects before. eg:
ProjectType = { Type = p.ProjectType.Type }
I believe that should be:
ProjectType = new ProjectTypeDTO{ Type = p.ProjectType.Type }

WebAPI [FromBody] as EF Model behaves like it is immutable

I have an WebAPI Controller that uses complex types from Entity Framework. When I receive the object I check to see if it exists. If it doesn't I'd like to create a new. Before I create a new I'd like to add a couple additional values to the object. If I add a break point and a watch I can see the value and it appears like it has changed. But the value doesn't make it to the database.
[Authorize(Roles ="customerprofileuser")]
[Route("api/CustomerProfile/Save")]
[HttpPost]
public IHttpActionResult SaveCustomerProfile([FromBody] MERP.Customer _input)
{
Models.Message.Response _return = new Models.Message.Response();
_return.Message = "Profile Saved!";
_return.Now = DateTime.Now;
try {
ERPEntities ent = new ERPEntities();
var cust = ent.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
if (cust == null)
{
_input.ID = Guid.NewGuid();
_input.Alias = getCustomerNumberNext(_input.Type);
_input.CreatedOn = DateTime.Now;
ent.Customers.Add(_input);
}
else
{
ent.Customers.Attach(_input);
ent.Entry(_input).State = System.Data.Entity.EntityState.Modified;
}
_return.ResponseObject = _input.ID.ToString();
ent.SaveChanges();
}
catch (Exception ex)
{
_return.Message = ex.Message;
_return.Severity = 3;
}
return Ok(_return);
}
If I map the values to a new object like this, everything works as expected.
var val = new Customer();
val.ID = Guid.NewGuid();
val.Active = _input.Active;
val.Alias = getCustomerNumberNext(_input.Type);
val.CreatedOn = DateTime.Now;
ent.Customers.Add(val);
I'd rather not map every single property to the new object property. Is there a way around this behavior?
Here's a sample of the auto-generated Customer class from my Entity Model.
public partial class Customer
{
public System.Guid ID { get; set; }
public string Name { get; set; }
public Nullable<System.Guid> Type { get; set; }
public string Alias { get; set; }
public string Website { get; set; }
public string Note { get; set; }
public string Email { get; set; }
public Nullable<System.Guid> Salesman { get; set; }
public Nullable<System.Guid> SalesRegion { get; set; }
public Nullable<bool> Active { get; set; }
public string LinkedIn { get; set; }
public string Facebook { get; set; }
public string Twitter { get; set; }
public string GoldmineFK { get; set; }
public string SalesFK { get; set; }
public string InventoryFK { get; set; }
public Nullable<System.Guid> Industry { get; set; }
public Nullable<System.Guid> Lead { get; set; }
public Nullable<System.Guid> Show { get; set; }
public Nullable<System.Guid> Territory { get; set; }
public Nullable<System.DateTime> CreatedOn { get; set; }
}
Here's the getCustomerNumberNext function
private string getCustomerNumberNext(Guid? companyid)
{
ERPEntities ent = new ERPEntities();
var _prefix = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberPrefix"
select p.Value.ToString()).FirstOrDefault();
var _number = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberSequence"
select p.Value.ToString()).FirstOrDefault();
var _newNumber = Convert.ToInt32(_number) + 1;
try
{
var _update = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberSequence"
select p).FirstOrDefault();
_update.Value = _newNumber.ToString();
ent.SaveChanges();
}
catch (Exception ex)
{ return ex.Message; }
return _prefix + _number;
}
EDIT: The C# code works as expected. The issue was with the data round tripping from the client and incompleteness.
I believe there is a typo in your question, which says "does make it to the database" but I believe you meant "does not make it to the database"
With that assumption I tried running similar code locally and was able to save the values as expected. The primary difference is that Alias is an integer in my code and I am assuming it is a complex class in your code. Here is the code that successfully saved the values to the database,
public class HomeController : ApiController
{
[HttpPost]
[Route("api/CustomerProfile/Save")]
public IHttpActionResult SaveCustomerProfile([FromBody] Customer _input)
{
masterEntities masterEntities = new masterEntities();
var cust = masterEntities.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
if (cust == null)
{
_input.ID = Guid.NewGuid();
_input.Alias = 0;
_input.CreatedOn = DateTime.Now;
masterEntities.Customers.Add(_input);
}
else
{
masterEntities.Customers.Attach(_input);
masterEntities.Entry(_input).State = System.Data.Entity.EntityState.Modified;
}
masterEntities.SaveChanges();
return Ok();
}
}
Here is what the generated Customer class like,
public partial class Customer
{
public System.Guid ID { get; set; }
public bool Active { get; set; }
public Nullable<int> Alias { get; set; }
public Nullable<System.DateTime> CreatedOn { get; set; }
}
Can you update your question with the Customer and Alias classes from your code and I can try reproducing that?
On a side note, I would suggest changing
var cust = ent.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
to
var cust = ent.Customers.AsNoTracking().FirstOrDefault(w => w.ID == _input.ID);

EntityFramework 5 - Conflicting changes to the role 'x' of the relationship 'x' have been detected

I have this model (Animal Model):
public int Id { get; set; }
public int AnimalSpecieId { get; set; }
public int AnimalBreedId { get; set; }
public Nullable<int> ProtectorId { get; set; }
public Nullable<int> OwnerId { get; set; }
public string Name { get; set; }
public virtual Owner Owner { get; set; }
public virtual Protector Protector { get; set; }
Protector Model:
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public string CellPhone { get; set; }
public string Email { get; set; }
public virtual ICollection<Animal> Animals { get; set; }
Owner Model:
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public string CellPhone { get; set; }
public string Email { get; set; }
public virtual ICollection<Animal> Animals { get; set; }
When I insert this model at the first time, if
ProtectorID = 1
and
OwnerID = null
it's ok, but, and I try to update this model, changing to:
OwnerID = 1
and
ProtectorID = null
I get the error in title, someone can help me with that ?
I don't agree with the above answer. I am not sure whether it solved your problem permanently because the issue is not related with the null value assignment. The actual reason is related with DBContext. When we go for any SaveChanges the context needs to be dispatched properly in order to proceed with the next SaveChanges to insert another record into DB on the same item with a different foreign key. You just need to add the below line after your "context.SaveChanges()"
context.Entry(your object).State = System.Data.Entity.EntityState.Detached;
This will solve the conflicts. Multiple insertion with same context results in conflicts.
Apologize if my comments criticised your answer in any manner.
I found the problem, after read this msdn post, I was thinking and found out what was happening, in my repository when I will update my entity, I was forgeting to set null all the related entities.
Old code:
var oldAnimal = context.Animals.Find(animal.Id);
if (oldAnimal != null)
{
oldAnimal.AnimalBreed = context.AnimalBreeds.Find(animal.AnimalBreed.Id);
oldAnimal.AnimalSpecie = context.AnimalSpecies.Find(animal.AnimalSpecie.Id);
oldAnimal.OwnerId = animal.OwnerId;
oldAnimal.ProtectorId = animal.ProtectorId;
oldAnimal.Castrated = animal.Castrated;
oldAnimal.DateBirth = animal.DateBirth;
oldAnimal.Gender = animal.Gender;
oldAnimal.Name = animal.Name;
oldAnimal.UpdateDate = DateTime.Now;
oldAnimal.Vaccinated = animal.Vaccinated;
oldAnimal.Weight = animal.Weight;
}
context.SaveChanges();
return animal;
new code:
var oldAnimal = context.Animals.Find(animal.Id);
if (oldAnimal != null)
{
oldAnimal.AnimalBreed = context.AnimalBreeds.Find(animal.AnimalBreed.Id);
oldAnimal.AnimalSpecie = context.AnimalSpecies.Find(animal.AnimalSpecie.Id);
oldAnimal.Owner = null;
oldAnimal.Protector = null;
oldAnimal.OwnerId = animal.OwnerId;
oldAnimal.ProtectorId = animal.ProtectorId;
oldAnimal.Castrated = animal.Castrated;
oldAnimal.DateBirth = animal.DateBirth;
oldAnimal.Gender = animal.Gender;
oldAnimal.Name = animal.Name;
oldAnimal.UpdateDate = DateTime.Now;
oldAnimal.Vaccinated = animal.Vaccinated;
oldAnimal.Weight = animal.Weight;
}
context.SaveChanges();
return animal;

EF4.1 - Attribute Evaluating to null at runtime

I'm using EF4.1 code first to create a simple database app with SQL CE 4 backend. I have a Product class and a CallItem class defined as so:
class CallItem
{
public int id { get; set; }
public float discount { get; set; }
public virtual Product Product { get; set; }
}
class Product
{
public int id { get; set; }
public decimal BaseCost { get; set; }
public int UnitSize { get; set; }
public bool isWasteOil { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Ingredients { get; set; }
}
edit - When I am creating a collection of CallItems using a LINQ query, I cannot access the attributes of the Product attached to each CallItem, eg
var callItems = from ci in context.CallItems select ci;
foreach(CallItem callItem in callItems)
{
RunSheet nrs = new RunSheet();
nrs.prodCode = callitem.Product.Code;
}
Interrogating the database shows that Productid in CallItems is being populated. However, the following line generates a NullReferenceException during run time:
nrs.prodCode = callitem.Product.Code;
Because callitem.Product is evaluating to null. Is this something to do with lazy loading and if so how can I resolve the issue?
RunSheet is another class, nrs is an instance whose attribute 'prodCode' I want to populate with the CallItem's Product's code.
Thanks!
From that code what you've showed it should work. Have you tried explicit loading?
var callItems = from ci in context.CallItems.Include(c => c.Product) select ci;
foreach(CallItem callItem in callItems)
{
RunSheet nrs = new RunSheet();
nrs.prodCode = callitem.Product.Code;
}
public class CallItem
{
public int Id { get; set; }
public float Discount { get; set; }
public virtual Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public decimal BaseCost { get; set; }
public int UnitSize { get; set; }
public bool IsWasteOil { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Ingredients { get; set; }
}
using (var context = new StackOverFlowContext())
{
var p = new Product
{
Id = 1,
BaseCost = 200,
Code = "Hola",
Description = "Soe description",
Ingredients = "Some ingredients",
IsWasteOil = true,
Name = "My Product",
UnitSize = 10
};
var item = new CallItem
{
Id = 101,
Discount = 10,
Product = p
};
context.CallItems.Add(item);
context.SaveChanges();
var result = from temp in context.CallItems
select temp;
Console.WriteLine("CallItem Id"+result.First().Id);
Console.WriteLine("ProductId"+result.First().Product.Id);
}
I wrote the above code with the following output
CallItemId 1
ProductId 1
The sql Profiler showed this
SELECT TOP (1)
[c].[Id] AS [Id],
[c].[Discount] AS [Discount],
[c].[Product_Id] AS [Product_Id]
FROM [dbo].[CallItems] AS [c]
It was too long for a comment ,so i put it here .

Categories

Resources