Lambda expressions - set the value of one property in a collection of objects based on the value of another property in the collection - .net-4.0

I'm new to lambda expressions and looking to leverage the syntax to set the value of one property in a collection based on another value in a collection
Typically I would do a loop:
class Item
{
public string Name { get; set; }
public string Value { get; set; }
}
void Run()
{
Item item1 = new Item { Name = "name1" };
Item item2 = new Item { Name = "name2" };
Item item3 = new Item { Name = "name3" };
Collection<Item> items = new Collection<Item>() { item1, item2, item3 };
// This is what I want to simplify.
for (int i = 0; i < items.Count; i++)
{
if (items[i].Name == "name2")
{
// Set the value.
items[i].Value = "value2";
}
}
}

LINQ is generally more useful for selecting data than for modifying data. However, you could write something like this:
foreach(var item in items.Where(it => it.Name == "name2"))
item.Value = "value2";
This first selects items that need to be modified and then modifies all of them using a standard imperative loop. You can replace the foreach loop with ForAll method that's available for lists, but I don't think this gives you any advantage:
items.Where(it => it.Name == "name2").ToList()
.ForEach(it => it.Value = "value2");
Note that you need to add ToList in the middle, because ForEach is a .NET 2.0 feature that's available only for List<T> type - not for all IEnumerable<T> types (as other LINQ methods). If you like this approach, you can implement ForEach for IEnuerable<T>:
public static void ForEach<T>(this IEnumerable<T> en, Action<T> f) {
foreach(var a in en) f(a);
}
// Then you can omit the `ToList` conversion in the middle
items.Where(it => it.Name == "name2")
.ForEach(it => it.Value = "value2");
Anyway, I'd prefer foreach loop, because that also makes it clear that you're doing some mutation - and it is useful to see this fact easily in the code.

Related

Return a list of elements that are NOT in two previous lists

I have 2 lists of IDs and I need to return a list with the products that aren't in any of those lists:
public IEnumerable<Produto> GetProdutosIdNotInFamily(Guid produtoId)
{
var produtosPai = GetListaPaisId(produtoId);
var produtosFilho = GetListaFilhosId(produtoId);
var prod = _dbContext.Produtos
.Where(u => !produtosPai.Any(p => p.ProdutoFilhoId == u.Id)
&& !produtosFilho.Any(p => p.ProdutoFilhoId == u.Id));
return prod;
}
You can do this in two ways -- One using Contains and other using Any like you provided in your snippet in the post.
Using Contains Method
If you want to use Contains() method, you may be pulling out all the product Ids into a collection and apply LINQ on top of it and get the list that is not part of both your reference lists. Sample code is as shown below
// This is the sample model I am dealing with
public class Dummy
{
public int Id { get; set; }
public string Name { get; set; }
}
// Assuming the below call returns list of 'Dummy' objects
var products = _dbContext.Produtos;
// list1 & list2 are populated in your case already through the method calls
var exclusionList1 = list1.Select(x => x.Id).ToList<int>();
var exclusionList2 = list2.Select(x => x.Id).ToList<int>();
var myList = products.Where(x => !exclusionList1.Contains(x.Id) && !exclusionList1.Contains(x.Id)).ToList();
Contains is an instance method and takes an object as a parameter and the time complexity depends on the collection you're using this on.
Using Any
Just like Where, Any is an extension method. It takes a delegate as a parameter which gives you greater flexibility and control with respect to what you would want to do.
Applying Any to your scenario is as shown below:
var products = _dbContext.Produtos;
var exclusionList1 = GetListaPaisId(produtoId);
var exclusionList2 = GetListaFilhosId(produtoId);
var prod = _dbContext.Produtos.Where(x => !exclusionList1.Any(z => x.Id == z.Id) &&
!exclusionList2.Any(z => x.Id == z.Id)).ToList();
You can choose your approach based on the context under which you are performing this operation.

Optimization of foreach statement, create new mapped Viewmodel list

Can someone please have a look at my code, I think there must be a way to optimize the foreach piece of code?
I have a database with Artists, each artist has multiple songTitles (called Titles), and each Title can have multiple Meanings.
Artist [1..*] Title [1..*] Meaning [0..*]
I want to find the count of Meanings, per Title, for an Artist, and return it as a new ViewModel List.
public class TitleVM
{
public int TitleID { get; set; }
public int MeaningCount { get; set; }
}
public List<TitleVM> GetTitlesByArtistID(int artistID)
{
//find the artist by ID
var titles = context.Titles.Where(x => x.ArtistID == artistID);
//create new VMList to be returned
var titleVMList = new List<TitleVM>();
//loop through each title,
foreach (var item in titles)
{
//find the number of meanings,
var count = 0;
if (item.Meanings != null && item.Meanings.Count > 0)
{
count = item.Meanings.Count();
}
// and map it to VM, add to list
titleVMList.Add(new TitleVM
{
TitleID = TitleID,
MeaningCount = count
});
}
return titleVMList;
}
I thought mapping it would be easiest, but have no idea how to map a viewmodel with lists in this way.
In my project I use Omu.ValueInjecter for mapping basic models, because Automapper needs full trust to run, and my host doesn't allow it.
Let me know if more information is needed.
Ok I read that its better to do an .AddRange then adding the the item with .Add each time.
I got my code down to the below:
public int CountMeanings(IEnumerable<Meaning> meanings)
{
if (meanings != null && meanings.Count() > 0)
return meanings.Count();
return 0;
}
public List<TitleVM> GetTitlesByArtistID(int artistID)
{
var titleVMList = new List<TitleVM>();
var titles = context.Titles.Where(x => x.ArtistID == artistID).AsEnumerable();
titleVMList.AddRange(titles.Select(item => new TitleVM {
TitleID = item.TitleID,
MeaningCount = CountMeanings(item.Meanings)
}));
return titleVMList;
}

how to set the selected value in the drop down list populated with ViewData in mvc

I am new to MVC, please help me to set the selected value in drop down list populated using View Data. I have gone through so many solutions but every solutions deals with selecting value for single drop down. I have same drop down listed using the foreach loop. Setting selected value for each dropdown in that foreach loop.
My code is shown below.
[In view]
int i = 0;
foreach (var item in Model.Select((x, j) => new { Data = x, Index = j + 1 })) {
#Html.DropDownListFor(model => item.Data.CategoryID,(IEnumerable<SelectListItem>)ViewData["categories"], new { #id = "category" + i })
i++;
}
[in controller]
SelectList selectList = new SelectList((IEnumerable<Category>)ConvertCategoryToList(dt1), "CategoryID", "CategoryName");
ViewData["categories"] = selectList;
There is a lot missing from your sample (e.g.what a category looks like, what the Edit actions look like, what is dt1, what the model is you pass to the view etc), but I will try based on what is shown.
Problem 1: Can't use foreach in binding
You can't use a foreach with a collection of form items. The items have no way of knowing which individual element to bind with (when they are sent back to the server they need more information to make each entry unique).
Solution:
It only knows how to bind if you use indexing (e.g. using for (int i=0; < count; i++){ #Html.DropDownListFor(model=>item[i]...)
Problem 2: Selected values only can come from SelectList!
This is the most irritating feature of DropDownListFor and appears to be a bug. It will not take the current selected value from the bound data. It will only take a current value from a SelectList, which means you need a unique SelectList for every dropdown.
Solution:
Create a unique SelectList in the view, per drop down required, with the item value as the current selection.
With some faking of your data, I got the following working:
Example
Controller:
Only store the list of items for the SelectList in the ViewBag:
ViewData["categories"] = ConvertCategoryToList(dt1);
View:
1. You need to pass a List and not an IEnumerable as IEnumerable cannot be indexed.
#model List<MyApplication.Item>
2. Create a SelectList for each dropdown
SelectList newSelectList = new SelectList((IEnumerable<MyApplication.Category>)ViewData["categories"], "CategoryID", "CategoryName", Model[i].Id);
3. Use the indexing method for addressing your items in the collection.
#Html.DropDownListFor(m => Model[i].Id, newSelectList, "Select an item")
Problem 3: What data to pass?
The other issue with your code, as-it-stands, is what to pass back and forth to/from the edit view? At the moment I would guess (from the variable name dt1) you are passing a DataTable(?). You will need to be very explicit about the data you are using in order to get a clean solution to this one. I would suggest posting a second question with all your code and Razor view HTML.
If you need to see more of my sample code (below), please post your own code so I can make them consistent.
Full dummy controller code below
public class TestController : Controller
{
public List<Category> dt1 { get; set; }
public TestController()
{
this.dt1 = new List<Category>();
for (int i = 1; i <= 10; i++)
{
this.dt1.Add(new Category() { CategoryId = i, CategoryName = string.Format("Category {0}", i) });
}
}
[HttpGet]
public ActionResult Edit()
{
//SelectList selectList = new SelectList((IEnumerable<Category>)ConvertCategoryToList(dt1), "CategoryID", "CategoryName");
ViewData["categories"] = ConvertCategoryToList(dt1);
List<Item> items = new List<Item>();
items.Add(new Item(){Id = 1});
items.Add(new Item(){Id = 2});
items.Add(new Item(){Id = 3});
items.Add(new Item(){Id = 4});
items.Add(new Item(){Id = 5});
return View(items);
}
[HttpPost]
public ActionResult Edit(FormCollection form)
{
// Turn form submission back into a compatible view model
List<Item> items = new List<Item>();
foreach (string key in form.Keys)
{
string val = form[key];
items.Add(new Item() { Id = int.Parse(val) });
}
// Recreate the select list
ViewData["categories"] = ConvertCategoryToList(dt1);
return View(items);
}
List<Category> ConvertCategoryToList(IEnumerable<Category> dt)
{
return dt.ToList();
}
}
Note: The post version of Edit simply recreates the list of data (using the selected values posted back) and returns to the Edit view. This is just for testing.
Screen shot
Dummy category class
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
Full test View
#model List<MyApplication.Item>
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Count; i++)
{
SelectList newSelectList = new SelectList((IEnumerable<MyApplication.Category>)ViewData["categories"], "CategoryID", "CategoryName", Model[i].Id);
#Html.DropDownListFor(m => Model[i].Id, newSelectList, "Select an item")
}
<input type="submit" value="Submit"/>
}

Web API Help pages - customizing Property documentation

I have my web api and I added the web api help pages to auto-generate my documentation. It's working great for methods where my parameters are listed out, but I have a method like this:
public SessionResult PostLogin(CreateSessionCommand request)
And, on my help page, it is only listing the command parameter in the properties section. However, in the sample request section, it lists out all of the properties of my CreateSessionCommand class.
Parameters
Name | Description | Additional information
request | No documentation available. | Define this parameter in the request body.
I would like it instead to list all of the properties in my CreateSessionCommand class. Is there an easy way to do this?
So, I managed to devise a workaround for this problem, in case anyone is interested.
In HelpPageConfigurationExtensions.cs I added the following extension method:
public static void AlterApiDescription(this ApiDescription apiDescription, HttpConfiguration config)
{
var docProvider = config.Services.GetDocumentationProvider();
var addParams = new List<ApiParameterDescription>();
var removeParams = new List<ApiParameterDescription>();
foreach (var param in apiDescription.ParameterDescriptions)
{
var type = param.ParameterDescriptor.ParameterType;
//string is some special case that is not a primitive type
//also, compare by full name because the type returned does not seem to match the types generated by typeof
bool isPrimitive = type.IsPrimitive || String.Compare(type.FullName, typeof(string).FullName) == 0;
if (!isPrimitive)
{
var properties = from p in param.ParameterDescriptor.ParameterType.GetProperties()
let s = p.SetMethod
where s.IsPublic
select p;
foreach (var property in properties)
{
var documentation = docProvider.GetDocumentation(new System.Web.Http.Controllers.ReflectedHttpParameterDescriptor()
{
ActionDescriptor = param.ParameterDescriptor.ActionDescriptor,
ParameterInfo = new CustomParameterInfo(property)
});
addParams.Add(new ApiParameterDescription()
{
Documentation = documentation,
Name = property.Name,
Source = ApiParameterSource.FromBody,
ParameterDescriptor = param.ParameterDescriptor
});
}
//since this is a complex type, select it to be removed from the api description
removeParams.Add(param);
}
}
//add in our new items
foreach (var item in addParams)
{
apiDescription.ParameterDescriptions.Add(item);
}
//remove the complex types
foreach (var item in removeParams)
{
apiDescription.ParameterDescriptions.Remove(item);
}
}
And here is the Parameter info instanced class I use
internal class CustomParameterInfo : ParameterInfo
{
public CustomParameterInfo(PropertyInfo prop)
{
base.NameImpl = prop.Name;
}
}
Then, we call the extension in another method inside the extensions class
public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId)
{
object model;
string modelId = ApiModelPrefix + apiDescriptionId;
if (!config.Properties.TryGetValue(modelId, out model))
{
Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions;
ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase));
if (apiDescription != null)
{
apiDescription.AlterApiDescription(config);
HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator();
model = GenerateApiModel(apiDescription, sampleGenerator);
config.Properties.TryAdd(modelId, model);
}
}
return (HelpPageApiModel)model;
}
The comments that are used for this must be added to the controller method and not the properties of the class object. This might be because my object is part of an outside library
this should go as an addition to #Josh answer. If you want not only to list properties from the model class, but also include documentation for each property, Areas/HelpPage/XmlDocumentationProvider.cs file should be modified as follows:
public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
if (reflectedParameterDescriptor != null)
{
if (reflectedParameterDescriptor.ParameterInfo is CustomParameterInfo)
{
const string PropertyExpression = "/doc/members/member[#name='P:{0}']";
var pi = (CustomParameterInfo) reflectedParameterDescriptor.ParameterInfo;
string selectExpression = String.Format(CultureInfo.InvariantCulture, PropertyExpression, pi.Prop.DeclaringType.FullName + "." + pi.Prop.Name);
XPathNavigator methodNode = _documentNavigator.SelectSingleNode(selectExpression);
if (methodNode != null)
{
return methodNode.Value.Trim();
}
}
else
{
XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
if (methodNode != null)
{
string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
if (parameterNode != null)
{
return parameterNode.Value.Trim();
}
}
}
}
return null;
}
and CustomParameterInfo class should keep property info as well:
internal class CustomParameterInfo : ParameterInfo
{
public PropertyInfo Prop { get; private set; }
public CustomParameterInfo(PropertyInfo prop)
{
Prop = prop;
base.NameImpl = prop.Name;
}
}
This is currently not supported out of the box. Following bug is kind of related to that:
http://aspnetwebstack.codeplex.com/workitem/877

EntityFramework, Insert if not exist, otherwise update

I'm having a Entity-Set Countries, reflecting a database table '<'char(2),char(3),nvarchar(50> in my database.
Im having a parser that returns a Country[] array of parsed countries, and is having issues with getting it updated in the right way. What i want is: Take the array of countries, for those countries not already in the database insert them, and those existing update if any fields is different. How can this be done?
void Method(object sender, DocumentLoadedEvent e)
{
var data = e.ParsedData as Country[];
using(var db = new DataContractEntities)
{
//Code missing
}
}
I was thinking something like
for(var c in data.Except(db.Countries)) but it wount work as it compares on wronge fields.
Hope anyone have had this issues before, and have a solution for me. If i cant use the Country object and insert/update an array of them easy, i dont see much benefict of using the framework, as from performers i think its faster to write a custom sql script that inserts them instead of ect checking if an country is already in the database before inserting?
Solution
See answer of post instead.
I added override equals to my country class:
public partial class Country
{
public override bool Equals(object obj)
{
if (obj is Country)
{
var country = obj as Country;
return this.CountryTreeLetter.Equals(country.CountryTreeLetter);
}
return false;
}
public override int GetHashCode()
{
int hash = 13;
hash = hash * 7 + (int)CountryTreeLetter[0];
hash = hash * 7 + (int)CountryTreeLetter[1];
hash = hash * 7 + (int)CountryTreeLetter[2];
return hash;
}
}
and then did:
var data = e.ParsedData as Country[];
using (var db = new entities())
{
foreach (var item in data.Except(db.Countries))
{
db.AddToCountries(item);
}
db.SaveChanges();
}
I would do it straightforward:
void Method(object sender, DocumentLoadedEvent e)
{
var data = e.ParsedData as Country[];
using(var db = new DataContractEntities)
{
foreach(var country in data)
{
var countryInDb = db.Countries
.Where(c => c.Name == country.Name) // or whatever your key is
.SingleOrDefault();
if (countryInDb != null)
db.Countries.ApplyCurrentValues(country);
else
db.Countries.AddObject(country);
}
db.SaveChanges();
}
}
I don't know how often your application must run this or how many countries your world has. But I have the feeling that this is nothing where you must think about sophisticated performance optimizations.
Edit
Alternative approach which would issue only one query:
void Method(object sender, DocumentLoadedEvent e)
{
var data = e.ParsedData as Country[];
using(var db = new DataContractEntities)
{
var names = data.Select(c => c.Name);
var countriesInDb = db.Countries
.Where(c => names.Contains(c.Name))
.ToList(); // single DB query
foreach(var country in data)
{
var countryInDb = countriesInDb
.SingleOrDefault(c => c.Name == country.Name); // runs in memory
if (countryInDb != null)
db.Countries.ApplyCurrentValues(country);
else
db.Countries.AddObject(country);
}
db.SaveChanges();
}
}
The modern form, using later EF versions would be:
context.Entry(record).State = (AlreadyExists ? EntityState.Modified : EntityState.Added);
context.SaveChanges();
AlreadyExists can come from checking the key or by querying the database to see whether the item already exists there.
You can implement your own IEqualityComparer<Country> and pass that to the Except() method. Assuming your Country object has Id and Name properties, one example of that implementation could look like this:
public class CountryComparer : IEqualityComparer<Country>
{
public bool Equals(Country x, Country y)
{
return x.Name.Equals(y.Name) && (x.Id == y.Id);
}
public int GetHashCode(Country obj)
{
return string.Format("{0}{1}", obj.Id, obj.Name).GetHashCode();
}
}
and use it as
data.Countries.Except<Country>(db, new CountryComparer());
Although, in your case it looks like you just need to extract new objects, you can use var newCountries = data.Where(c => c.Id == Guid.Empty); if your Id is Guid.
The best way is to inspect the Country.EntityState property and take actions from there regarding on value (Detached, Modified, Added, etc.)
You need to provide more information on what your data collection contains i.e. are the Country objects retrieved from a database through the entityframework, in which case their context can be tracked, or are you generating them using some other way.
I am not sure this will be the best solution but I think you have to get all countries from DB then check it with your parsed data
void Method(object sender, DocumentLoadedEvent e)
{
var data = e.ParsedData as Country[];
using(var db = new DataContractEntities)
{
List<Country> mycountries = db.Countries.ToList();
foreach(var PC in data)
{
if(mycountries.Any( C => C.Name==PC.Name ))
{
var country = mycountries.Any( C => C.Name==PC.Name );
//Update it here
}
else
{
var newcountry = Country.CreateCountry(PC.Name);//you must provide all required parameters
newcountry.Name = PC.Name;
db.AddToCountries(newcountry)
}
}
db.SaveChanges();
}
}