How to bind data to DTO attribute based on a condition? - c#

I'm trying to bind data to a variable known as CurrentControlled that is defined in my DTO (Data transfer object), The issue is that i want to assign a string based on what data is returned from the database. In this case i have a table in my database known as RiskActions which has a column known as ActionCompleteDate, if the data inside ActionCompleteDate has date value in it than the string assigned to CurrentControlled should be 'Current', whereas if there is no date present inside ActionCompleteDate then the value for CurrentControlled should be Controlled.
I have tried assigning a condition to CurrentControlled but for some reason the compiler gives me an error.
var actions = dataContext.RiskInstances.Where(riskInstancePredicate.Compile());
actions = actions
.OrderBy(a => a.RiskInstanceID2)
.ThenBy(a=> a.Issue.Question);
List<RiskDTO> results = actions
.Select(ra => new RiskDTO
{
CurrentControlled = if(ra.RiskActions.Where(m => m.ActionCompleteDate == null)) {
TerminologyFactor.Parse("{Current}",TerminologyFactor.RMMonitor)
}
else {
TerminologyFactor.Parse("{Controlled}",TerminologyFactor.RMMonitor)
}
})

You need to use the conditional operator. Also, this is a guess because I don't know your data structure here, but you likely need to use Any instead of Where as it returns a boolean you can use as the condition:
List<RiskDTO> results = actions
.Select(ra => new RiskDTO
{
CurrentControlled = ra.RiskActions.Any(m => m.ActionCompleteDate == null))
? TerminologyFactor.Parse("{Current}",TerminologyFactor.RMMonitor)
: TerminologyFactor.Parse("{Controlled}",TerminologyFactor.RMMonitor)
}

Related

Generic method with dynamically selected parameter in predicate

I have many objects of diffrent type and i need to check few diffrent properties from each of them in the same way. I want to use this method inside object initializer like this:
collection.GroupBy(x => x.Identifier)
.Select(group => new SomeType
{
Identifier = group.Key,
Quantity = group.Sum(i => i.Quantity),
Type = MY_METHOD_HERE(group),
Name = MY_METHOD_HERE(group)
})
Strongly typed example for one of the properties:
private ItemType CheckItemTypeConsistency(IGrouping<string, Item> group)
{
if (group.Any(x => x.ItemType != group.First().ItemType))
{
throw new ArgumentException($"Item with number {group.Key} is inconsistent", nameof(Item.ItemType));
}
else
{
return group.First().ItemType;
}
}
But i also have other property in Item that needs to be checked in the same way with diffrent type, so i have similar method but .ItemType is changed everywhere to .Name and return type is string.
I also have diffrent object type that i need to use it for, so in another method Item is changed to Vehicle.
How to create generic method like that?
I tried something like this:
private TElement CheckConsistency<TKey, TElement>(IGrouping<TKey, TElement> group, (maybe something here?))
{
if (group.Any(x => x.(what here?) != group.First().(what here?)))
{
throw new ArgumentException($"Element with number {group.Key} is inconsistent");
}
else
{
return group.First();
}
}
I solved problem with returning value by returning whole item so i can just CheckConsistency().Property when invoking this method.
But i dont know what to do with (what here?).
I thought that maybe i can put something in (maybe something here?) that would be somehow used in place of (what here?)?
Any ideas? I'm not sure about reflection because this method could be called easily more than 1000 times depending on collection size and number of unique entries.
#Edit:
Lets say it's something like merging data from two files. For example 2 data sets of items where quantity is added together for items with the same identifier etc. but some properties should be the same like name and if they are diffrent then there's something wrong and i need to throw an error.
There are diffrent sets of data with completly diffrent properties like vehicles, but rules are similar, some fields are just added together etc, some must be identical.
Using an accessor function and genericizing the property type and object type, you have:
private TProp CheckConsistency<TClass, TProp>(IGrouping<string, TClass> group, Func<TClass, TProp> propFn) {
var firstPropValue = propFn(group.First());
if (group.Any(x => firstPropValue == null ? propFn(x) == null : !propFn(x).Equals(firstPropValue))) {
throw new ArgumentException($"Item with number {group.Key} is inconsistent");
}
else {
return firstPropValue;
}
}
Which you can use like:
var ans = collection.GroupBy(x => x.Identifier)
.Select(group => new SomeType {
Identifier = group.Key,
Quantity = group.Sum(i => i.Quantity),
Type = CheckConsistency(group, x => x.ItemType),
Name = CheckConsistency(group, x => x.Name)
});
If it is important to include the proper argument name in the exception, you could pass it in, take in an Expression<Func<>> and pull out the name, then compile the argument to a lambda to use (may be slow), or use reflection instead of a lambda property accessor (also possibly slow).
To use Reflection, I recommend caching the compiled function so you don't constantly re-compile each time the method is called:
// [Expression] => [Func]
Dictionary<LambdaExpression, Delegate> propFnCache = new Dictionary<LambdaExpression, Delegate>();
private TProp CheckConsistency<TClass, TProp>(IGrouping<string, TClass> group, Expression<Func<TClass, TProp>> propExpr) {
Func<TClass,TProp> propFn;
if (propFnCache.TryGetValue(propExpr, out var propDel))
propFn = (Func<TClass, TProp>)propDel;
else {
propFn = propExpr.Compile();
propFnCache.Add(propExpr, propFn);
}
var firstPropValue = propFn(group.First());
if (group.Any(x => !propFn(x).Equals(firstPropValue))) {
throw new ArgumentException($"Item with number {group.Key} is inconsistent", ((MemberExpression)propExpr.Body).Member.Name);
}
else {
return firstPropValue;
}
}

NHibernate: How to load only specific properties in a list of entities (instead of full object)?

I have been using NHibernate in my project and it is working very good until the time.
Now I am facing one problem and don't know how to solve it.
Problem is I want to fetch only x columns from table instead of all and then bind this to original entity with fetched columns assigned values where for others should show null.
e.g. I have table Employee as
FieldName DataType
ID int
FName nvarchar(500)
LName nvarchar(500)
Resume nvarchar(max)
From above I want to fetch only Id, FName, LName only and bind it to Employee entity and then we would like to load Resume by making separate database call using NHibernate.
I tried
var list = session
.CreateCriteria(typeof (Employee))
.SetProjection(Projections
.ProjectionList()
.Add(Projections.Property("Id"))
.Add(Projections.Property("FName"))
.Add(Projections.Property("LName")))
.List();
but above code returns restricted column data but in form of array of object.
I wanted this to get bind to entity instead of object array.
So, please share your thoughts on this
You need to set the ResultTransformer and call ToList() with the type, otherwise NHibernate didn't know what to do with the projections. And you need also to specifiy the target property names in your Employee type.
Your query can look simething like this:
var list = session
.CreateCriteria(typeof (Employee))
.SetProjection(Projections
.ProjectionList()
.Add(Projections.Property("Id"), "Id")
.Add(Projections.Property("FName"), "FirstName")
.Add(Projections.Property("LName"), "LastName"))
.SetResultTransformer(Transformers.AliasToBean<Employee>())
.List<Employee>();
Please note: "Id", "FirstName" and "LastName" must be properties in the Employee type.
Answer from #Rabban explains it well; +1 to him. I am just putting it differently.
As you observed, SetProjection will return array of objects. To convert objects to entities, you need to call SetResultTransformer(Transformers.AliasToBean<Entity>(). The code sample is already given in other answer; I will not repeat it.
Problem with your current code is that, you need to type in column names as string. I generally avoid this approach. I prefer to use strongly typed version of same with IQueryOver as below:
public IList<TEntity> GetList<TEntity>(ProjectionList columnList, Junction where, int top) where TEntity : BaseEntity
{
IQueryOver<TEntity> query = null;
if((columnList != null) && (where != null))
{
query = session.QueryOver<TEntity>()
.Select(columnList)
.TransformUsing(Transformers.AliasToBean<TEntity>())
.Where(where);
}
else if((columnList != null) && (where == null))
{
query = session.QueryOver<TEntity>()
.Select(columnList)
.TransformUsing(Transformers.AliasToBean<TEntity>());
}
else if((columnList == null) && (where != null))
{
query = session.QueryOver<TEntity>()
.Where(where);
}
else
{
query = session.QueryOver<TEntity>();
}
IList<TEntity> result = query.Take(top).List();
return result;
}
You can pass strongly typed list of columns to this method as below:
ProjectionList columnList = Projections.ProjectionList();
columnList.Add(Projections.Property<Employee>(x => x.Id));
...
...

Asp.Net Mvc delete columns from database table

I am trying to delete all table data where username = current user name. But there is some error on lambda expression and conversion."Cannot convert source type to target type. Any help?
public JsonResult DeleteChatMessages(int id)
{
string toId = Session["SessionUserName"].ToString();
tblChat tblchats = _obj.tblChat.Where(p => p.ToId == toId);
_obj.tblChat.Remove(tblchats);
_obj.SaveChanges();
return this.Json(true, JsonRequestBehavior.AllowGet);
}
I am assuming you are expecting only one object to return from the Where filter.
Change your line as,because as per this . The Where clause returns an IEnumerable<TSource>. And you are assigning that to the non IEnumerable object.
tblChat tblchats = _obj.tblChat.FirstOrDefault(p => p.ToId == toId)
and you also might want to check for the Null, before removing it from the tblChat.
If you are expecting more than one object to match the filter and want to remove all of them then use #tmg approach.
_obj.tblChat.RemoveRange(_obj.tblChat.Where(p => p.ToId == toId).ToList());
Filter the "chats" you want.
var tblchats = _obj.tblChat.Where(p => p.ToId == toId).ToList();
After iterate throught them and remove.
foeach(chat in tblchats){
_obj.tblChat.Remove(chat);
}
_obj.SaveChanges();

Passing object to linq result

I get an item from the list to manuplate it.
App.CurrentQuestion = App.AllQuestionList[dataSourceRowIndex];
After manuplations I save new data to database and get back from database into App.CurrentQuestion, so App.CurrentQuestion's list reference breaks. I want to update list and trying to focus selected item by linq
App.AllQuestionList
.Where(q => q.qID == App.CurrentQuestion.qID)
.FirstOrDefault() = App.CurrentQuestion;
but I get an error like "The left-hand side of an assignment must be a variable, property or indexer"
I can use this method
for (int i = 0; i < App.AllQuestionList.Count; i++)
{
if (App.AllQuestionList[i].qID == App.CurrentQuestion.qID)
{
App.AllQuestionList[i] = App.CurrentQuestion;
break;
}
}
but looking for an alternative method. Or is there any faster method?
You shouldn't have to do anything, since it's by reference.
App.CurrentQuestion = App.AllQuestionList[dataSourceRowIndex];
Whatever change you make in App.CurrentQuestion should be reflected in the App.AllQuestionList
App.AllQuestionList[App.AllQuestionList.IndexOf(App.AllQuestionList.
.Where(q => q.qID == App.CurrentQuestion.qID)
.First())] = App.CurrentQuestion;
Edit: you can just use the IndexOf to find the index of the object you wanted to find by LINQ query

c# copied property loses reference when referenced object is removed from list

Example
Have a look at the following code:
private void DeDuplicateOrganisations()
{
var profileOrgs = _organisations.Where(o => o.ExistsInProfile).ToList();
var kvkOrgs = _organisations.Where(o => !o.ExistsInProfile).ToList();
profileOrgs.ForEach(o =>
{
var duplicate = kvkOrgs.FirstOrDefault(k => k.KvK == o.KvK || k.Title == o.Title);
if (duplicate != null)
{
o.CompanyInfoOrganisation = duplicate.CompanyInfoOrganisation;
o.ExistsInBoth = true;
kvkOrgs.Remove(duplicate);
}
});
_organisations = profileOrgs.Concat(kvkOrgs).OrderBy(o => o.Title).ToList();
}
In this example the property CompanyInfoOrganisation (simply a get; set; property) is copied when an organisation is considered a duplicate. This all works as expected, duplicates are nicely deduplicated.
Also this is true inside this message:
_organisations.First(o => o.ExistsInBoth).CompanyInfoOrganisation != null;
Problem
Now I bind the _organisations list to a listbox
lbxCompanies.DataSource = null;
lbxCompanies.DataSource = _organisations;
lbxCompanies.DisplayMember = "Title";
lbxCompanies.SelectedIndex = -1;
and later on get the selected value:
var org = lbxCompanies.SelectedValue as Organisation;
gbxCompanyInfo.Visible = org != null;
if (gbxCompanyInfo.Visible)
if (org.CompanyInfoOrganisation != null)
// NEVER GETS HERE (but gbxComanpyInfo is visible)
If I try to read the CompanyInfoOrganisation property I always get null while I know the property was set.
Question
What is happening here? How come the property reference is destroyed? How can I prevent this from happening?
The reference you're using only has immediate scope and as soon as the query ends it exits scope and your reference disappears. So when you bind later, the reference is exactly right -- null.
profileOrgs.ForEach(o =>
{
// Right here -- var duplicate has scope ONLY within your query.
// As soon as the query is executed it leaves scope and the reference
// pointer will be null
var duplicate = kvkOrgs.FirstOrDefault(k => k.KvK == o.KvK || k.Title == o.Title);
if (duplicate != null)
{
o.CompanyInfoOrganisation = duplicate.CompanyInfoOrganisation;
o.ExistsInBoth = true;
kvkOrgs.Remove(duplicate);
}
});
Because you're using a class, you need to perform a deep MemberwiseClone on it to get a NEW copy of the object:
o.CompanyInfoOrganisation = (YourInfoType)duplicate.CompanyInfoOrganisation.MemberwiseClone();
When you load the data, load the CompanyInfoOrganisation property along with the root entity; that way it will be already loaded into memory. If using LINQ to SQL, you load via DataLoadOptions, and pass this to the context. If using Entity Framework, you use the Include method in the LINQ query.
It might have to do with capturing of variables inside the lambda. Try substituting the .ForEach to a regular foreach().
Or maybe the CompanyInfoOrganisation in duplicate was null to begin with.
The problem was I used string.Join() to show the values, and the first value to join was null (which is really annoying), resulting in an empty string, leaving me thinking the property was null. However it turned out the property was not null, but has a perfectly valid reference to the object needed. Using the debugger with a little more care would have saved me an hour or so...
Sorry!

Categories

Resources