I have the following property in my model:
//PRODUCTS
private ICollection<int> _productIds;
[NotMapped]
public ICollection<int> ProductIds
{
get { return Products.Select(s => s.Id).ToList(); }
set { _productIds = value; }
}
When my code returns a new instance of this model, the set accessor doesn't seem to take. In other words, I can see that the get accessor is appropriately returning a collection of product ids, but when I attempt to assign using the set, the value is an empty List<int>. For example:
var result = new Application
{
Id = application.Id,
. . .
ProductIds = application.ProductIds //<--this is a list of integers,
// but the new "result" object shows an empty list.
};
It is very unusual to have get and set for one property to work of different sources. You may want to remove set altogether if you always read the value from somewhere else.
Maybe you are looking to override value of the property (i.e. for unit testing) like this:
[NotMapped]
public ICollection<int> ProductIds
{
get { return _productIds != null ?
_productIds // return one that was "set"
: Products.Select(s => s.Id).ToList(); // read from DB
}
set { _productIds = value; }
}
Related
I have a view model DetailViewModel contains one IList of a sub view model EntryDetailViewModel.
public class DetailViewModel
{
[NotMapped]
[JsonIgnore]
private IList<EntryDetailViewModel> _entries;
[JsonIgnore]
public new IList<EntryDetailViewModel> Entries
{
get
{
if (_entries == null)
_entries = new List<=EntryDetailViewModel>();
return _entries.Where(m => !m.Deleted).ToList();
}
set => _entries = value;
}
}
My issue is when adding a EntryDetailViewModel entryDetailModel into the collection, the collection simply remains count = 0.
detailModel.Entries.Add(entryDetailModel);
Any ideas? Is it because entryDetailModel at the moment has no Id so it cannot be added? Or what can it be?
Additional Info
I tried query an existing DetailViewModel dvm1, and a existing EntryDetailViewModel from another DetailViewModel to add into dvm1, it also failed. Does that mean I can't add view model into another view model?
Nope, it won't work that way.
When you call:
detailModel.Entries.Add(entryDetailModel);
This triggers the Getter for your Entries, which is doing this:
return _entries.Where(m => !m.Deleted).ToList();
_entries may have been set to a new List<Entry> however, by returning the .Where(...).ToList(), the getter will return a newly initialized list containing just non-deleted entries. You're adding to that temporary list of results, not _entries.
If you want your Getters to filter only undeleted rows, that is fine, but you should consider a more of a DDD approach:
[NotMapped]
[JsonIgnore]
private IList<EntryDetailViewModel> _entries = new List<EntryDetailViewModel>();
[JsonIgnore]
public new IReadOnlyList<EntryDetailViewModel> Entries
{
get
{
return _entries.Where(m => !m.Deleted).ToList().AsReadOnly();
}
}
public void AddEntry(EntryDetailViewModel entry)
{
// TODO: Validate entry doesn't already exist, is complete/valid...
// then...
_entries.Add(entry);
}
With collection sets it's beneficial to initialize them on creation to avoid needing that extra boiler plate to check for null or constructor initialization. Then the getter is marked as a ReadOnlyList (or ReadOnlyCollection) which returns your filtered data. Using ReadOnly variants isn't required but it gives your future consumers at least some warning that says "Hey, you shouldn't try and modify this collection." DDD involves exposing suitable add/update/delete methods to regulate when and how the state of this object can be changed. There is no Setter for this collection so it cannot be overwritten by some rogue code added later. This involves planning out methods to serve as actions to regulate how state is modified rather than relying on setters.
Thanks to Steve Py for pointing out calling model.entries.add(entry) will always trigger the getter, which result to returning a new list every time.
However, the solution will always return a new list of the viewmodel when query and mapped. My original code doesn't post this issue, so I integrated them together by adding the AddEntry function, which will not trigger the getter and still able to query all the necessary data. My solution is down below.
[AutoMap(typeof(Manifest), ReverseMap = true)]
public class ViewModel
{
......other stuff
[NotMapped]
[JsonIgnore]
private IList<EntryViewModel> _entries;
[JsonIgnore]
public virtual IList<EntryViewModel> Entries
{
get
{
if (_entries == null)
_entries = new List<EntryViewModel>();
return _entries.Where(m => !m.Deleted).ToList();
}
set => _entries = value;
}
public void AddEntry(EntryViewModel entry)
{
_entries.Add(entry);
}
}
[AutoMap(typeof(Manifest), ReverseMap = true)]
public class DetailViewModel : ViewModel
{
......other stuff
[NotMapped]
[JsonIgnore]
private IList<EntryDetailViewModel> _entries;
[JsonIgnore]
public new IList<EntryDetailViewModel> Entries
{
get
{
if (_entries == null)
_entries = new List<EntryDetailViewModel>();
return _entries.Where(m => !m.Deleted).ToList();
}
set => _entries = value;
}
public void AddEntry(EntryDetailViewModel entry)
{
_entries.Add(entry);
}
}
In the table column (of type nvarchar) I store an array of string values (List <string>). To get a data model I use Entity Framework.
Model:
namespace Project.Models.Atm
{
public class MyObject
{
[JsonIgnore]
public string _Parameters { get; set; }
[NotMapped]
[JsonProperty("parameters")]
public List<string> Parameters
{
get
{
return JsonConvert.DeserializeObject<List<string>>(string.IsNullOrEmpty(_Parameters) ? "" : _Parameters);
}
set
{
_Parameters = JsonConvert.SerializeObject(value);
}
}
}
}
If a new list of string values is assigned to the field of the myObject.parameters object, then I will do everything well.
MyObject myObject = new MyObject();
List<string> parameters = new List<string>();
parameters.Add("value");
myObject.parameters = parameters;
But if you try to add a value to the list of objects one by one, they are not added.
MyObject myObject = new MyObject();
myObject.parameters.Add("value"); <-- The value will not be added and there will be no errors.
What could be the problem?
While executing:
myObject.parameters.Add("value");
you call:
get
{
return JsonConvert.DeserializeObject<List<string>>(string.IsNullOrEmpty(_Parameters) ? "" : _Parameters);
}
and this code is creating new list for you and you are adding to it not to the string that is actually stored in the database.
To avoid temptation of adding to this list change your property to return IEnumerable<string> instead and always assign new list to the property.
I have a typical web API with a couple of PUT/UPDATE endpoints. These endpoints simply call the underlying service, and do the update.
The service layer, has the typical signature such as Object Update(Object object). What I then do is I basically run the following pseudo code:
var dbobject = _db.Object.Find(object.Id);
dbobject.Field1 = object.Field1;
dbobject.Field2 = object.Field2;
// continue for all fields
_db.SaveChanges();
return GetObjectById(object.Id);
However, this provides a challenge for me.
Lets say we have a consumer of our API. This consumer calls my PUT endpoint (/api/Object/{id}), and the payload is the updated Object.
However, lets say that the object we put don't know about example Field4, then this value would be NULL after the update has been run.
My question is:
What do you do about all those fields the payload does NOT contain?
How do you handle not setting values to NULL you don't expect to be
NULL afterwards?
As one of the possible ways, here can be used mix of NotifyPropertyChanged with automapper
The Idea is to store in DTO object which fields exactly was set, and which stays filled with default value. And use collected data in mapping.
For example DTO object will be
public class Dto
{
private List<string> Changed = new List<string>();
public bool IsChanged(string field) => Changed.Contains(field);
private int _age;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// IMPORTANT: field name should fit main object field name
Changed.Add("Name");
}
}
public int Age
{
get { return _age; }
set
{
_age = value;
Changed.Add("Age");
}
}
}
I used Next class for test
public class Human
{
public string Name { get; set; } = "DEFAULT";
public int Age { get; set; } = -1;
}
and automapper configuration will looks like
cfg.CreateMap<Dto, Human>()
.ForAllMembers(s=> s.Condition(d=>d.IsChanged(s.DestinationMember.Name)));
This is a simple example. But it still doesn't prevent to use function IsChanged for some complex/specific logic, use not just a strings but Expressions / MethodInfo, or add custom attributes and use them in automapper configuration (DestinationMember is MethodInfo)
Append
Instead of complex DTO object the information about passed field you can get from Request.Properties in your controller (key ms_querynamevaluepairs value of type Dictionary<string, string>).
I have a next index:
public class TestIndex : AbstractIndexCreationTask<Resource>
{
public class Result
{
public string Caption { get; set; }
public string TestVal{ get; set; }
}
public TestIndex()
{
Map = resources => from r in resources
select new
{
Caption = r.Caption,
TestVal = r.Caption
};
}
}
And that's how I query it:
var data = session.Query<Resource, TestIndex>()
.Customize(x => x.WaitForNonStaleResults())
.AsProjection<TestIndex.Result>()
.ToList();
The problem is that after query TestVal property is null for each object, when Caption is filled with expected value.
If you want to do a projection from the index, you need to store that value
I had similar problem, query on the index still retruned null values. It turned out that i did to fast test - after each program running index was also runing and it had no enough time do biild up. In this case sulution is use customization.WaitForNonStaleResultsAsOfNow():
Query<ResultType,IndexType>()
.Customize(customization => customization.WaitForNonStaleResultsAsOfNow()) //this is imprtant when you using quick tests, not in production server
.Where(...).AsProjection<ResultType>
You also need remember about storing data from index to DB and use at the end in index class:
StoreAllFields(FieldStorage.Yes);
can i set value for a property like this
public string MyProperty { get { return _GetValue(); } set { _GetValue(); } }
public static string _GetValue()
{
string name = null;
using (HROEF.Entities context = new HROEF.Entities())
{
var result = (from my linq Statement).First().ToString();
name = result;
}
return name;
}
In my View
#Html.DisplayFor(model=>Model.MyProperty)
is there any thing wrong in this?
It is not displaying the value in my view
any help?
In general, you shouldn't be doing database accessin properties. It's just bad practice. Properties should not perform lengthy operations that can time out, or have other issues.
As for why it's not showing your value, that's hard to say. Most likely, your linq query simply isn't returning any results.