EF6 setting default property disables change tracking - c#

Working with EF6 WebApi on .NET 4.7 and hitting an issue when trying to save a model update when we're setting a default value. I'm using the below pattern to save a JSON property in EF6, and it's worked well for a number of classes:
public class User
{
...
public int AddressId { get; set; }
public string AddressName { get; set; }
// New fields for mapping 1-N Address
[NotMapped]
public List<Address> Addresses
{
get { return JsonConvert.DeserializeObject<List<Address>>(AddressesJson ?? string.Empty); }
set { AddressesJson = JsonConvert.SerializeObject(value); }
}
[MaxLength(5000), JsonIgnore]
public string AddressesJson { get; set; }
...
}
public class Address
{
public string Id { get; set; }
public string Name { get; set; }
}
The issue arises when we try to set a default value. We want to migrate the existing Address on the User object if the Addresses == null. So we tried the below:
public class User
{
...
[NotMapped]
public List<Address> Addresses
{
get { return AddressesJson == null ? AddressesDefault : JsonConvert.DeserializeObject<List<Address>>(AddressesJson ?? string.Empty); }
set { AddressesJson = JsonConvert.SerializeObject(value); }
}
[MaxLength(5000), JsonIgnore]
public string AddressesJson { get; set; }
[NotMapped]
internal List<Address> AddressesDefault {
get
{
var defaultAddress = new List<Address>();
defaultAddress.Add(new Address() { Id = AddressId, Name = AddressName });
return defaultAddress;
}
}
...
}
I would have assumed that if the AddressesJson backing field was null, I would return a new object that is populated with some default values, which is working. But when I do a HTTP PUT to update the Addresses field (and hence the AddressesJson backing field), it's not being set and is always returning AddressesDefault.
What am I missing? Any suggestions? Many thanks!

Sorry my english. I have no reputation to comment, so I will comment here in the Answer section:
First remark: when you're using Addresses getter, you're already checking AddressesJson == null, so you don't need call AddressesJson ?? string.Empty, because at this point, AddressesJson will never be null.
You can remove ?? string.Empty.
Second: have you been using Addresses.Add() at some point?
Note when you add an address with Addresses.Add(someAddress), your AddressesJson is not changing.
If true, in this case, I recommend the following approach:
Create an AddAddress() method inside the User class. Then you can update AddressesJson:
public void AddAddress(Address newAddress)
{
// add to list
Addresses.Add(newAddress);
// updates json with new address added
AddressesJson = JsonConvert.SerializeObject(Addresses);
}
Hope I could help.

Related

Asp.Net Core Generic Repository Pattern Soft Delete

I'm trying to create a Soft Delete action in my Repository but i have to do that without creating any interfaces or classes. Let me show u to my method first,
public void Delete(T model)
{
if (model.GetType().GetProperty("IsDelete") == null )
{
T _model = model;
_model.GetType().GetProperty("IsDelete").SetValue(_model, true);//That's the point where i get the error
this.Update(_model);
}
else
{
_dbSet.Attach(model);
_dbSet.Remove(model);
}
}
im getting a Object reference not set to an instance of an object. exception. of course i know what that means but i just could not figure it out and i dont know what to do. I'm not sure if there is a better way or not.
Thanks for reading!
Guys u really gotta look at to the where i get the error. I'm editing my question.
public abstract class Base
{
protected Base()
{
DataGuidID = Guid.NewGuid();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid DataGuidID { get; set; }
public int? CreatedUserId { get; set; }
public int? ModifiedUserId { get; set; }
public string CreatedUserType { get; set; }
public string ModifiedUserType { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
public bool? IsDelete { get; set; } //That's the property
}
Every type of model class is inheriting from that Base class. When i create a new object, it takes null value. That is why im controlling that property as
==null .
First you check if the property IsDelete is null, and then you try to set the value of the property which is, obviously, null.
if (model.GetType().GetProperty("IsDelete") == null ) should be
if (model.GetType().GetProperty("IsDelete") != null )
Edit:
Now we know that you want to check the value of a nullable bool, we have to take another approach.
// first we get the property of the model.
var property = model.GetType().GetProperty("IsDelete");
// lets assume the property exists and is a nullable bool; get the value from the property.
var propertyValue = (bool?)property.GetValue(model);
// now check if the propertyValue not has a value.
if (!propertyValue.HasValue)
{
// set the value
property.SetValue(model, true);
...
}

C# How to initialize an object containing a list<T> with standard values

This question is related to this question. I managed to get one step further, but I am now unable to initialize my whole object with default values in order to prevent it from being null at list level. The goal of this is to hand down the "null" values to my SQL query. Ultimately what I want is one record in my DB that will express: This row has been recorded, but the related values were "null".
I have tried Brian's fiddle and it does not seem to work for me to initialize the whole model with standard values.
Expectation: Upon object initialisation the "null" values should be used and then overwritten in case there is a value coming through JSON deserialisation.
Here is what I have tried. None of this will have the desired effect. I receive this error:
Application_Error: System.ArgumentNullException: Value cannot be null.
Every time I try to access one of the lists in the data model.
namespace Project.MyJob
{
public class JsonModel
{
public JsonModel()
{
Type_X type_x = new Type_X(); // This works fine.
List<Actions> action = new List<Actions>(); // This is never visible
/*in my object either before I initialise JObject or after. So whatever
gets initialised here never makes it to my object. Only Type_X appears
to be working as expected. */
action.Add(new Actions {number = "null", time = "null", station =
"null", unitState = "null"}) // This also does not prevent my
//JsonModel object from being null.
}
public string objectNumber { get; set; }
public string objectFamily { get; set; }
public string objectOrder { get; set; }
public string location { get; set; }
public string place { get; set; }
public string inventionTime { get; set; }
public string lastUpdate { get; set; }
public string condition { get; set; }
public Type_X Type_X { get; set; }
public List<Actions> actions { get; set; }
}
public class Actions
{
public Actions()
{
// None of this seems to play a role at inititialisation.
count = "null";
datetime = "null";
place = "null";
status = "null";
}
// public string count { get; set; } = "null"; should be the same as above
// but also does not do anything.
public string count { get; set; }
public string datetime { get; set; }
public string place { get; set; }
public string status { get; set; }
}
public class Type_X
{
public Type_X
{
partA = "null"; // This works.
}
public string partA { get; set; }
public string PartB { get; set; }
public string partC { get; set; }
public string partD { get; set; }
public string partE { get; set; }
}
}
This is how I now initialize the object based on Brian's answer.
JObject = JsonConvert.DeserializeObject< JsonModel >(json.ToString(), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore});
When I try to iterate over Actions' content, it (logically) gives me above mentioned null error.
for (int i = 0, len = JObject.actions.Count(); i < len; i++)
My current understanding of constructor initialisations:
If I define values such as count = "null"; they should appear in any new object that is created.
If default values are present I would then also expect that a list that has items with default values (such as count for ex.) would be of Count() 1 and not null. How is that even possible?
This will get you out of your bind:
private List<Actions> _actions = new List<Actions>();
public List<Actions> actions { get => _actions; set => _actions = value ?? _actions; }
This causes trying to set actions to null to set it to the previous value, and it is initially not null so it can never be null.
I'm not absolutely sure I'm reading your question right, so here's the same fragment for partA:
private string _partA = "null";
public string partA { get => _partA; set => _partA = value ?? _partA; }
I have found that in some cases, initializing generic lists with their default constructor on your model increases ease of use. Otherwise you will always want to validate they are not null before applying any logic(even something as simple as checking list length). Especially if the entity is being hydrated outside of user code, i.e. database, webapi, etc...
One option is to break up your initialization into two parts. Part 1 being the basic initialization via default constructor, and part 2 being the rest of your hydration logic:
JObject = new List < YourModel >();
... < deserialization code here >
Alternatively you could do this in your deserialization code, but it would add a bit of complexity. Following this approach will allow you to clean up your code in other areas since each access will not need to be immediately proceeded by a null check.

Include only one property, not entire database row

Model:
public class Word
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime? WhenCreated { get; set; }
public ApplicationUser Author { get; set; }
[NotMapped]
public string AuthorName
{
get
{
if (Author != null)
{
return Author.UserName;
}
else {
return "";
}
}
}
public List<Definition> Definitions { get; set; }
}
Controller:
[HttpGet]
public IEnumerable<Word> Get()
{
return _db.Words.Include(x=>x.Author).ToList();
}
My Controller now returns entire ApplicationUser class which is one of properties of Word. I want to send only one property of ApplicationUser: UserName. How can I do that?
I've added AuthorName, which would return only data that I want from ApplicationUser. Unfortunately I still have to .Include(x=>x.Author) to make this property work. Can I somehow omit including Author in process of data serialization (to hide it when sending data to user)?
I know I can use .Select() method, but it requires me to type all properties I will need. If I modify my Model in the future, I will need to update all those .Select() which will would be inconvenient and waste of time.
How would you solve that?
You need to create a Dto object and assign the values to it and return the Dto instead.
Dto
public class WordDto
{
public string Title { get; set; }
public DateTime? WhenCreated { get; set; }
public string AuthorName { get; set; }
}
Then in your action
[HttpGet]
public async Task<IEnumerable<WordDto>> Get()
{
return _db.Words
.Include(x=>x.Author)
.Select(x =>
new WordDto
{
Title = x.Title,
DateTime = x.WhenCreated,
AuthorName = x.Author?.UserName ?? string.Empty
}
)
.ToListAsync();
}
You can try it as shown below.
Note : You don't need to use Include here.
[HttpGet]
public async Task<IEnumerable<Word>> Get()
{
return _db.Words.Select(x => new
{
Word = x,
AuthorName = x.Author.UserName
}
).ToList();
}
Create a View model and use AutoMapper to populate. Look at using AutoMapper and ProjectTo extension https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions
That way if you add properties to View model they will be automatically mapped if they exist on your EF model
So create a VM with required properties named appropriately (see AutoMapper docs on naming conventions):
public class WordVM
{
public string Title { get; set; }
public DateTime? WhenCreated { get; set; }
public string AuthorUserName { get; set; }
}
Then use AutoMapper to project (it will do any required includes so if you changed the VM later then it would handle that)
_db.Words.ProjectTo<WordVM>().ToList();
You don't need the NotMapped property AutoMapper would map the navigation property Author and the Author Property UserName to AuthorUserName
My workaround was to get all the related entities with .include(), then loop over them and omit the property values I did not want to return. It would require some maintenance in case your model changed, but surprisingly, it did not impact the response time dramatically.

Custom Deserialize of Properties with Json.net

I'm using Json.net api JsonConvert.PopulateObject which accepts two parameters first the json string and second the actual object which you want to fill.
The structure of the object that I want to fill is
internal class Customer
{
public Customer()
{
this.CustomerAddress = new Address();
}
public string Name { get; set; }
public Address CustomerAddress { get; set; }
}
public class Address
{
public string State { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
My json string is
{
"Name":"Jack",
"State":"ABC",
"City":"XX",
"ZipCode":"098"
}
Now the Name property gets filled becuase it is present in json string but the CustomerAddress is not getting populated. Is there any way by which I can tell the Json.net library that populate CustomerAddress.City from the City property in the json string ?
Directly - no.
But it should be possible to achieve that, e.g. here is an attempt (assuming you can't change json):
class Customer
{
public string Name { get; set; }
public Address CustomerAddress { get; set; } = new Address(); // initial value
// private property used to get value from json
// attribute is needed to use not-matching names (e.g. if Customer already have City)
[JsonProperty(nameof(Address.City))]
string _city
{
set { CustomerAddress.City = value; }
}
// ... same for other properties of Address
}
Other possibilities:
change json format to contain Address object;
custom serialization (e.g. using binder to serialize fake type and convert it to needed);
... (should be more).

Only update parameters where the value is specified by client

I have a servicestack service which accepts a DTO that looks like this:
[Route("/appointment/{id}", Verbs = "POST")]
public class UpdateAppointment
{
public Guid Id { get; set; }
public DateTime StartTime { get; set; }
public int Duration { get; set; }
public string Description { get; set; }
public Guid? MemberId { get; set; }
}
How can I check whether the MemberId value was set by the client since "null" is a valid value. Normally if NULL is not a valid value, I could use the PopulateWithNonDefaultValues() method.
So the result should be that if I don't specify MemberId in my HTTP POST payload, I want the server to not update the value.
I hope that makes sense..
This is not normally an issue if you consider that the client always provides all values when calling the UpdateAppointment Service. So I'd highly recommend that you consider every property is a "valid" value provided by the client and update all fields.
Create a separate Service if only want to update a partial property list.
If I really needed to check whether the client provided a value you can specify a different value in the Request DTO constructor, e.g:
public class UpdateAppointment
{
public UpdateAppointment()
{
MemberId = Guid.Empty;
}
//...
}
where a non Guid.Empty value means it was populated by the client.
Or you could also use a calculated Property:
public class UpdateAppointment
{
[IgnoreDataMember]
public bool HasMemberId { get; set; }
Guid? memberId;
public Guid? MemberId
{
get { return memberId; }
set
{
memberId = value;
HasMemberId = true;
}
}
}
A more fragile alternative is to buffer the Request with the global Request Filter:
appHost.PreRequestFilters.Add((httpReq, httpRes) => {
httpReq.UseBufferedStream = true;
});
Which will retain a copy of the Request Stream which you can get a copy of in your Service with:
var jsonBody = base.Request.GetRawBody();
var hasMemberId = jsonBody.ToLower().Contains("memberid");
Although note this is serializer dependent, i.e. wont work with Binary Serializers like ProtoBuf or MsgPack.
Why is MemberId nullable if null value is not allowed?
Just change its definition to:
public Guid MemberId { get; set; }
have you tried load the values first then editing it before update?
I mean this:
[HttpPost]
public ActionResult Update(UpdateAppointment UAForm){
UpdateAppointment ua = new UpdateAppointmentBll().Find(UAForm.Id);
ua.StartTime = UAForm.StartTime;
//so no...
if(UAForm.MemberId != null)
ua.MemberId = UAForm.MemberId;
new UpdateAppointmentBll().Save(ua);
return View();
}

Categories

Resources