I have this code:
List<MyObjectOne> myListOne = new List<MyObjectOne>(){new MyObjectOne { ID = 1, field2 = 2}};
List<MyObjectTwo> myListTwo = new List<MyObjectTwo>(){new MyObjectTwo { ID = 4, field6 = "string"}};
bool hasSomething = false;
var result = new[] { new {ID = 0 }}.ToList();
if (hasSomething)
{
// Use list one.
result = myListOne.Select(x => new { ID = x.ID});
}
else
{
// Use list two.
result = myListTwo.Select(x => new { ID = x.ID });
}
foreach (var item in result)
{
// Some logic to manipulate item.ID.
item.ID;
}
What I trying to do it's to use the same anonymous type to select a list of IDs from two different lists. So I use the Select(x => new { ID = x.ID }) in order to create the anonymous type for each table in order to have only one for loop.
The error raised is "Cannot implicitly convert type IEnumerable to List"
¿any idea?
Assuming ID in MyObjectOne and MyObjectTwo are both int's, your code will work if you replace ToList with AsEnumerable:
var result = new[] { new { ID = 0 } }.AsEnumerable();
If the ID properties are some other type (e.g. long's), you need to specify that when creating the anonymous type here:
var result = new[] { new { ID = 0L } }.AsEnumerable();
Or like this:
var result = new[] { new { ID = (long)0 } }.AsEnumerable();
However, this kind of code is kind of confusing, and I wouldn't recommend it for a production environment. Here's an alternative solution that avoids creating a 'dummy' object just for implicit anonymous typing:
var result = hasSomething
? myListOne.Select(x => new { ID = x.ID })
: myListTwo.Select(x => new { ID = x.ID });
Related
my question is simple but I got stuck with something. Can you tell me how can I reduce 2 select into 1 select LINQ in c#? I am using CloudNative.CloudEvents NuGet package for cloud-native events.
var orderEvents = input
.Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent())
.Select(_ =>
new CloudEvent()
{
Type = _.EventType,
Subject = _.Subject,
Source = _.Source,
Data = _
});
input is a parameter from cosmosDbTrigger it`s type : IReadOnlyList
OrderDocument.cs
public class OrderDocument
{
public string Id { get; private set; }
public string Json { get; private set; }
public OrderDocument(string id, string json)
{
Id = id;
Json = json;
}
public OrderEvent ToOrderEvent() => OrderEventHelper.ToOrderEvent(Json);
}
OrderEventHelper.cs
public static OrderEvent ToOrderEvent(string json)
{
ArgumentHelper.ThrowIfNullOrEmpty(json);
var orderEvent = JsonConvert.DeserializeObject<OrderEvent>(json);
var eventDefinition = OrderEvents.EventDefinitions.SingleOrDefault(_ => _.EventType == orderEvent.EventType);
return eventDefinition == null
? orderEvent
: new OrderEvent(
orderEvent.Id,
orderEvent.Source,
orderEvent.EventType,
orderEvent.Subject,
orderEvent.DataContentType,
orderEvent.DataSchema,
orderEvent.Timestamp,
JsonConvert.DeserializeObject(orderEvent.Payload.ToString(), eventDefinition.PayloadType),
orderEvent.TraceId);
}
linq extensions are basically for loops in the background. If you want to perform multiple actions against a list, perhaps making your own simple for loop where you can manage that yourself would work.
Your code:
var orderEvents = input
.Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent())
.Select(_ =>
new CloudEvent()
{
Type = _.EventType,
Subject = _.Subject,
Source = _.Source,
Data = _
});
could be changed to:
// our result set, rather than the one returned from linq Select
var results = new List<CloudEvent>();
foreach(var x in input){
// create the order event here
var temporaryOrderEvent = new OrderDocument(x.Id, x.ToString()).ToOrderEvent();
// add the Cloud event to our result set
results.Add(new CloudEvent()
{
Type = temporaryOrderEvent .EventType,
Subject = temporaryOrderEvent .Subject,
Source = temporaryOrderEvent .Source,
Data = temporaryOrderEvent
});
}
where you then have a result list to work with.
If you wanted to keep it all in linq, you could instead perform all of your logic in the first Select, and ensure that it returns a CloudEvent. Notice here that you can employ the use of curly brackets in the linq statement to evaluate a function rather than a single variable value:
var orderEvents = input
.Select(x =>
{
// create the order event here
var temporaryOrderEvent = new OrderDocument(x.Id, x.ToString()).ToOrderEvent();
// return the Cloud event here
return new CloudEvent()
{
Type = temporaryOrderEvent .EventType,
Subject = temporaryOrderEvent .Subject,
Source = temporaryOrderEvent .Source,
Data = temporaryOrderEvent
};
});
How about putting conversion to OrderEvent and using ToCloudEvent in the same Select?
var orderEvents = input
.Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent().ToCloudEvent())
public class OrderEvent
{
public CloudEvent ToCloudEvent()
{
new CloudEvent()
{
Type = this.EventType,
Subject = this.Subject,
Source = this.Source,
Data = this
};
}
}
I have a code like this:
using (var ws = new WebService())
using (var db = new EntityFrameworkModel())
{
var originalFolders = ws.GetFolders();
foo.folders = originalFolders.Select(c => new FolderType()
{
Id = c.Description,
Items = ws.ListDocs(c.Id)
.Select((d, i) =>
new DocType()
{
Id = StaticMethod(d, c),
Order = i,
SomeValue = db.docs.Single(doc => doc.Id == StaticMethod(d, c)).SomeValue
}
).ToArray()
}).ToArray();
}
But I get a "LINQ to Entities does not recognize the method 'StaticMethod' method, and this method cannot be translated into a store expression" exception. Does exist any way to pass a static value as a parameter? Something like this:
using (var ws = new WebService())
using (var db = new EntityFrameworkModel())
{
var originalFolders = ws.GetFolders();
foo.folders = originalFolders.Select(c => new FolderType()
{
Id = c.Description,
Items = ws.ListDocs(c.Id)
.Select((d, i, string myValue = StaticMethod(d, c)) =>
new DocType()
{
Id = myValue,
Order = i,
SomeValue = db.docs.Single(doc => doc.Id == myValue).SomeValue
}
).ToArray()
}).ToArray();
}
I can't modify DocType class constructor. Does exist any way?
Usually this is a matter of making sure you don't inline functions in linq-to-SQL expressions that can't be turned into valid SQL.
Try this:
using (var ws = new WebService())
using (var db = new EntityFrameworkModel())
{
var originalFolders = ws.GetFolders();
foo.folders = originalFolders.Select(c => new FolderType()
{
Id = c.Description,
Items = ws.ListDocs(c.Id)
.Select((d, i) =>
{
var id = StaticMethod(d, c);
return new DocType()
{
Id = id,
Order = i,
SomeValue = db.docs.Single(doc => doc.Id == id).SomeValue
};
}).ToArray()
}).ToArray();
}
Here is class UserArrived:
public class UserArrived{
public string id{get;set;}
}
Here is class OldUser:
public class OldUser{
public string id{get;set;}
public DateTime lastArrived{get;set;}
}
And here is class User:
public class User{
public string id{get;set;}
public Boolean newUser{get;set;}
}
Finally, here is two List:
List<UserArrived> UserArrivedList=new List<UserArrived>();
List<OldUser> OldUserList=new List<OldUser>();
All the id in each class is unique.
Now I need to combine UserArrived and OldUser to a brand new List<User>.
As we know, the user arrives the shop may is a new user or an old user. If the user id in UserArrived also contains in OldUser, the property newUser in the new List is false for true.
In my opinion, I will combine two List into one first and then use the distinct method to remove the duplicates.
However, it seems the distinct can not run with a condition.
Although I can use several foreach to solve this while I feel it is so troublesome. I want to use something easy just like lambda or linq. How can I achieve this?
=============================
Here is an example of the input:
List<UserArrived> UserArrivedList=new List<UserArrived>(){new UserArrived(){id="A"},new UserArrived(){id="B"},new UserArrived(){id="C"}};
List<OldUser> OldUserList=new List<OldUser>(){new OldUser(){id="B",lastArrived=DateTime.Now}};
the output is:
A,true
B,false
C,true
If I understand your requirement you're saying that if an id is in both lists then the user is an old user, otherwise it is a new user.
So here's the simplest way that I could come up with to do it:
IEnumerable<User> users =
Enumerable
.Concat(
UserArrivedList.Select(i => i.id),
OldUserList.Select(i => i.id))
.ToLookup(x => x)
.Select(x => new User() { id = x.Key, newUser = x.Count() == 1 });
Let's test with some input:
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B" },
new OldUser() { id = "C" },
};
Here are my results:
B is the only user who appears in both lists so should be False.
So, there's a bit of confusion about the requirements here.
The OP has added a concrete example of the input data and the expected output.
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
new UserArrived() { id = "C" }
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B", lastArrived = DateTime.Now }
};
With this input the OP is expecting True, False, True for A, B, C respectively.
Here is the code of the four current answers:
var results = new []
{
new
{
answered = "Enigmativity",
users = Enumerable
.Concat(
UserArrivedList.Select(i => i.id),
OldUserList.Select(i => i.id))
.ToLookup(x => x)
.Select(x => new User() { id = x.Key, newUser = x.Count() == 1 })
},
new
{
answered = "JQSOFT",
users = UserArrivedList.Select(x => x.id)
.Concat(OldUserList.Select(y => y.id))
.Distinct()
.Select(x => new User
{
id = x,
newUser = OldUserList.Count(o => o.id == x) == 0,
})
},
new
{
answered = "Anu Viswan",
users =
UserArrivedList
.Join(OldUserList, ual => ual.id, oul => oul.id, (ual, oul) => new User { id = oul.id, newUser = false })
.Concat(UserArrivedList.Select(x => x.id).Except(OldUserList.Select(x => x.id))
.Concat(OldUserList.Select(x => x.id).Except(UserArrivedList.Select(x => x.id)))
.Select(x=> new User{ id = x, newUser = true}))
},
new
{
answered = "Barns",
users =
UserArrivedList.Select(i => i.id)
.Union(OldUserList.Select(i => i.id))
.Select(j => new User
{
id = j,
newUser =
!(UserArrivedList.Select(i => i.id).Contains(j)
&& OldUserList.Select(i => i.id).Contains(j))})
}
};
That gives the output of:
So, currently all of the answers presented match the OP's example.
I'd be interested in the OP commenting on this as the input data:
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B" },
new OldUser() { id = "C" },
};
When I run this I get this output:
Here three users match and one does not.
This all boils down to what the description means:
As we know, the user arrives the shop may is a new user or an old user. If the user id in UserArrived also contains in OldUser, the property newUser in the new List is false for true.
The thing about LINQ--it isn't always easy. In fact it can get quit cluttered. In the question statement I read,
I want to use something easy just like lambda or linq.
Well, that is relative. But, I think that when using LINQ, one should try to keep it simple. Even break the statement down into multiple statements if necessary. For that reason I propose this solution (demonstrated in a console app):
static void Main(string[] args)
{
Console.WriteLine();
Console.WriteLine("--------------------Test This Code -----------------------");
var combined = TestUserCombined();
//The following is just to demonstrate the list is populated properly
combined.OrderBy(s => s.id.PadLeft(4, '0')).ToList().ForEach(k => Console.WriteLine($"X id: {k.id} | isNew:{k.newUser}"));
}
private static IEnumerable<User> TestUserCombined()
{
List<UserArrived> userArrivedList=new List<UserArrived>();
List<OldUser> oldUserList=new List<OldUser>();
//populate the lists...
for(int i = 0; i < 20; i+=2)
{
var userArrived = new UserArrived();
userArrived.id = i.ToString();
userArrivedList.Add(userArrived);
}
for(int i = 0; i < 20; i+=3)
{
var oldUser = new OldUser();
oldUser.id = i.ToString();
oldUserList.Add(oldUser);
}
//Now for the solution...
var selectedUserArrived = userArrivedList.Select(i => i.id);
var selectedOldUser = oldUserList.Select(i => i.id);
var users = selectedUserArrived
.Union(selectedOldUser)
.Select(j => new User{id=j,newUser=!(selectedUserArrived.Contains(j) && selectedOldUser.Contains(j))});
return users;
}
Certainly, this all could have been done in one statement, but I believe this makes it more readable and understandable.
EDIT:
There has been some discussion amongst the coders posting solutions as to exactly what conditions must be met in order for the value "newUser" to be set to "true". It was my understanding from the initial posted question that the "id" must be present in both lists "UserArrivedList" AND "OldUserList", but I tend to agree with #JQSOFT that it makes more sense that the only condition that must be met should be that the "id" need only be present in "OldUserList". If that is indeed the case than the Select() expression above should be .Select(j => new User{id=j,newUser=!selectedOldUser.Contains(j)});
I hope I understood your query. One way to achieve this using Linq would be
var users = UserArrivedList.Join(OldUserList,ual=>ual.id,oul=>oul.id,(ual,oul)=>new User{id=oul.id,newUser=false})
.Concat(UserArrivedList.Select(x=>x.id).Except(OldUserList.Select(x=>x.id))
.Concat(OldUserList.Select(x=>x.id).Except(UserArrivedList.Select(x=>x.id)))
.Select(x=> new User{id=x,newUser=true}));
Now you need to create a distinct list of User type from two lists of different types; UserArrived and OldUser objects. A user is identified by a unique id of string type.
Accordingly, I'd suggest this:
var users = UserArrivedList.Select(x => x.id)
.Concat(OldUserList.Select(y => y.id))
.Distinct()
.Select(x => new User
{
id = x,
newUser = OldUserList.Count(o => o.id == x) == 0,
}).ToList();
Which gets the unique ids from both UserArrivedList and OldUserList and creates new User object for each. The OldUserList.Count(o => o.id == x) == 0, assigns false to the newUser property if the user id exists in the OldUserList otherwise true.
We are having a hard time trying to figure out the best way to handle this with out declaring a loop after you get the data.
For instance take this piece of code: (Data2 is tied to Data with a foreign key)
context.Data.Select(_ => new DataModel
{
Id = _.Id,
Data2 = new Data2Model
{
Id = _.Data2.Id,
Name = _.Data2.Name,
Date = _.Data2.Date
},
Date = _.Date
});
If _.Data2 is not null then this runs correctly but if _.Data2 happens to be null then this will error. The way we are getting around this now is to add Data2Id to our DataModel and then loop through all of the records to get the information if its not null.
var lst = context.Data.Select(_ => new DataModel
{
Id = _.Id,
Data2Id = _.Data2ID
Date = _.Date
}).ToList();
foreach(var item in lst)
{
if (item.Data2Id != null)
{
var dataItem = context.Data2.FirstOrDefault(_ => _.Id == item.Data2Id);
item.Data2 = new Data2Model
{
Id = dataItem.Id,
Name = dataItem.Name,
Date = dataItem.Date
}
}
}
Is there a cleaner / better way to keep this in the original select loop.
Thanks
Try:
Data2 = _.Data2 == null ? null : new Data2Model
{
Id = _.Data2.Id,
Name = _.Data2.Name,
Date = _.Data2.Date
},
context.Data.Select(_ => new DataModel
{
Id = _.Id,
Data2 = _.Data2 == null ?
new Data2Model{Id = _.Data2ID} :
new Data2Model{ Id=_.Data2.Id, Name=_.Data2.Name, Data=_.Data2.Date},
Date = _.Date
});
I'm making the assumption that if it is null you still have the _.Data2ID at hand?
Using the ternary operator
If Data2 is null then return a new Data2Model with just the other _.Data2ID value
If it is not null then go ahead and create a new Data2Model with all the details.
You can extract the logic in separate method to shorten your LINQ query and potentially reuse your code:
private static DataModel Map(DataModel _)
{
Data2Model model = _.Data2 ?? new Data2Model();
return new DataModel
{
Id = _.Id,
Date = _.Date,
Data2 = new Data2Model
{
Id = model.Id,
Name = model.Name,
Date = model.Date
}
};
}
Your query than becomes:
context.Data.Select(Map);
You should replace the artificial types with your own.
I have a ControlMeasure table that holds information on each control measure and a ControlMeasurepeopleExposed Table that holds a record for each person exposed in the control measure this could be 1 record or many records.
I Have a controller that populates a List view
For each item in the list, Control Measure, I would like to create a string that shows all the People at risk
e.g.
PeopleString = "Employees, Public, Others";
Ive added a foreach in the controller to show what I'm trying to do however I'm aware that this wont work.
The controller is this:
public ActionResult ControlMeasureList(int raId)
{
//Populate the list
var hazards = new List<Hazard>(db.Hazards);
var controlMeasures = new List<ControlMeasure>(db.ControlMeasures).Where(x => x.RiskAssessmentId == raId);
var cmcombined = (
from g in hazards
join f in controlMeasures
on new { g.HazardId } equals new { f.HazardId }
select new CMCombined
{
Activity = f.Activity,
ControlMeasureId = f.ControlMeasureId,
ExistingMeasure = f.ExistingMeasure,
HazardName = g.Name,
LikelihoodId = f.LikelihoodId,
Rating = f.Rating,
RiskAssessmentId = f.RiskAssessmentId,
SeverityId = f.SeverityId,
}).OrderBy(x => x.Activity).ToList();
var cmPeopleExp = new List<ControlMeasurePeopleExposed>(db.ControlMeasurePeopleExposeds).Where(x => x.RiskAssessmentId == raId);
var peopleExp = from c in cmPeopleExp
join d in db.PeopleExposeds
on c.PeopleExposedId equals d.PeopleExposedId
orderby d.Name
select new RAPeopleExp
{
RAPeopleExpId = c.PeopleExposedId,
PeopleExpId = c.PeopleExposedId,
PeopleExpName = d.Name,
RiskAssessmentId = c.RiskAssessmentId,
ControlMeasureId = c.ControlMeasureId
};
var model = cmcombined.Select(t => new FullControlMeasureListViewModel
{
ControlMeasureId = t.ControlMeasureId,
HazardName = t.HazardName,
LikelihoodId = t.LikelihoodId,
Rating = t.Rating,
SeverityId = t.SeverityId,
Activity = t.Activity,
ExCM = t.ExistingMeasure,
//This section here is where I'm struggling
var PeopleString = new StringBuilder();
foreach (var p in peopleExp)
{
PeopleString.AppendLine(p.PeopleName);
{
PeopleExposed = PeopleString,
});
return PartialView("_ControlMeasureList", model);
}
I know I cant directly put this code in the controller but it does represent what I want to do.
You can't foreach within an object initializer (which is what you're trying to do when instantiating FullControlMeasureListViewModel). You can, however, use a combination of string.Join and peopleExp.Select:
var model = cmcombined.Select(t => new FullControlMeasureListViewModel
{
//other props
PeopleExposed = string.Join(",", peopleExp
.Where(p => p.ControlMeasureId == t.ControlMeasureId)
.Select(p => p.PeopleExpName));
//other props
});