Edit: At first I thought this was related to component views, but I've managed to isolate the issue a bit.
I have the following class:
public class TmpContext
{
public NYPContext db { get; set; }
public IEnumerable<SelectListItem> GetUserListFromSelection(int[] selection)
{
var userList = from u in db.UsuariosIntranet
join su in selection on u.Id equals su
select new SelectListItem()
{
Text = string.Format("{0}, {1}", u.ApellidoPaterno, u.Nombres),
Value = u.Id.ToString(),
};
return userList;
}
}
I have a view that receives the class above as model, and have the following code in it:
#{
// First list
var list1 = new int[] { 4947850 };
var a = Model.GetUserListFromSelection(list1);
foreach (var user in a)
{
<p class="tag">#user.Text</p>
}
// Second list, note the different ids
var list2 = new int[] { 2, 3 };
var b = Model.GetUserListFromSelection(list2);
foreach (var user in b)
{
<p class="tag">#user.Text</p>
}
// Third list
var o = list1.ToList();
// add a new id
o.Add(5185969);
// int[] otra = ;
var c = Model.GetUserListFromSelection(o.ToArray());
foreach (var user in c)
{
<p class="tag">#user.Text</p>
}
}
The expected results are three different lists, but somehow I get the first items repeated three times.
Is this expected behaviour?
It appears to be a bug, and it is already fixed. Talk about fast turnaround!
https://github.com/aspnet/EntityFramework/issues/2826
Related
If I have a class like this
`
class Person
{
public string First;
public string Last;
public bool IsMarried;
public int Age;
}`
Then how can I write a LINQ Expression where I could select properties of a Person. I want to do something like this (user can enter 1..n properties)
SelectData<Person>(x=>x.First, x.Last,x.Age);
What would be the input expression of my SelectData function ?
SelectData(Expression<Func<TEntity, List<string>>> selector); ?
EDIT
In my SelectData function I want to extract property names and then generate SELECT clause of my SQL Query dynamically.
SOLUTION
Ok, so what I have done is to have my SelectData as
public IEnumerable<TEntity> SelectData(Expression<Func<TEntity, object>> expression)
{
NewExpression body = (NewExpression)expression.Body;
List<string> columns = new List<string>();
foreach(var arg in body.Arguments)
{
var exp = (MemberExpression)arg;
columns.Add(exp.Member.Name);
}
//build query
And to use it I call it like this
ccc<Person>().SelectData(x => new { x.First, x.Last, x.Age });
Hopefully it would help someone who is looking :)
Thanks,
IY
I think it would be better to use delegates instead of Reflection. Apart from the fact that delegates will be faster, the compiler will complain if you try to fetch property values that do not exist. With reflection you won't find errors until run time.
Luckily there is already something like that. it is implemented as an extension function of IEnumerable, and it is called Select (irony intended)
I think you want something like this:
I have a sequence of Persons, and I want you to create a Linq
statement that returns per Person a new object that contains the
properties First and Last.
Or:
I have a sequence of Persns and I want you to create a Linq statement
that returns per Person a new object that contains Age, IsMarried,
whether it is an adult and to make it difficult: one Property called
Name which is a combination of First and Last
The function SelectData would be something like this:
IEnumerable<TResult> SelectData<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TResult> selector)
{
return source.Select(selector);
}
Usage:
problem 1: return per Person a new object that contains the
properties First and Last.
var result = Persons.SelectData(person => new
{
First = person.First,
Last = person.Last,
});
problem 2: return per Person a new object that contains Age, IsMarried, whether he is an adult and one Property called Name which is a combination
of First and Last
var result = Persons.SelectData(person => new
{
Age = person.Name,
IsMarried = person.IsMarried,
IsAdult = person.Age > 21,
Name = new
{
First = person.First,
Last = person.Last,
},
});
Well let's face it, your SelectData is nothing more than Enumerable.Select
You could of course create a function where you'd let the caller provide a list of properties he wants, but (1) that would limit his possibilities to design the end result and (2) it would be way more typing for him to call the function.
Instead of:
.Select(p => new
{
P1 = p.Property1,
P2 = p.Property2,
}
he would have to type something like
.SelectData(new List<Func<TSource, TResult>()
{
p => p.Property1, // first element of the property list
p -> p.Property2, // second element of the property list
}
You won't be able to name the returned properties, you won't be able to combine several properties into one:
.Select(p => p.First + p.Last)
And what would you gain by it?
Highly discouraged requirement!
You could achive similar result using Reflection and Extension Method
Model:
namespace ConsoleApplication2
{
class Person
{
public string First { get; set; }
public string Last { get; set; }
public bool IsMarried { get; set; }
public int Age { get; set; }
}
}
Service:
using System.Collections.Generic;
using System.Linq;
namespace Test
{
public static class Service
{
public static IQueryable<IQueryable<KeyValuePair<string, object>>> SelectData<T>(this IQueryable<T> queryable, string[] properties)
{
var queryResult = new List<IQueryable<KeyValuePair<string, object>>>();
foreach (T entity in queryable)
{
var entityProperties = new List<KeyValuePair<string, object>>();
foreach (string property in properties)
{
var value = typeof(T).GetProperty(property).GetValue(entity);
var entityProperty = new KeyValuePair<string, object>(property, value);
entityProperties.Add(entityProperty);
}
queryResult.Add(entityProperties.AsQueryable());
}
return queryResult.AsQueryable();
}
}
}
Usage:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var list = new List<Person>()
{
new Person()
{
Age = 18,
First = "test1",
IsMarried = false,
Last = "test2"
},
new Person()
{
Age = 40,
First = "test3",
IsMarried = true,
Last = "test4"
}
};
var queryableList = list.AsQueryable();
string[] properties = { "Age", "Last" };
var result = queryableList.SelectData(properties);
foreach (var element in result)
{
foreach (var property in element)
{
Console.WriteLine($"{property.Key}: {property.Value}");
}
}
Console.ReadKey();
}
}
}
Result:
Age: 18
Last: test2
Age: 40
Last: test4
Lets say I have a list of asteroid objects like so:
9_Amphitrite
24_Themis
259_Aletheia
31_Euphrosyne
511_Davida
87_Sylvia
9_Metis
41_Daphne
Each asteroid has a title, a StartRoationPeriod, and a EndRoationPeriod.
I need to concatenate their names based on how close the current asteroid StartRoationPeriod and previous asteroid EndRoationPeriod are to an orbital constant and then spit out the concatenated title.
So with the above list, the final objects may look like this:
9_Amphitrite
24_Themis;259_Aletheia
31_Euphrosyne;511_Davida;87_Sylvia
9_Metis
41_Daphne
This requires me to keep track of both the current and previous asteroids.
I started to write the loop, but I'm unsure of where or even how to check the current asteroids start rotation period against the previous asteroids end rotation period...basically, it just gets messy fast...
string asteroid_title = string.Empty;
Asteroid prev_asteroid = null;
foreach (var asteroid in SolarSystem)
{
if (prev_asteroid != null)
{
if (asteroid.StartRoationPeriod + OrbitalConstant >= prev_asteroid.EndRoationPeriod)
{
asteroid_title = asteroid_title + asteroid.Title;
} else {
asteroid_title = asteroid.Title;
yield return CreateTitle();
}
}
prev_evt = evt;
}
I think this should work for you (If aggregate looks too complex try to convert it to a foreach,it's easy)
using System;
using System.Collections.Generic;
using System.Linq;
namespace Program
{
class Asteroid
{
public int EndRoationPeriod { get; internal set; }
public string Name { get; internal set; }
public int StartRoationPeriod { get; internal set; }
}
class AsteroidGroup
{
public int EndRoationPeriod { get; internal set; }
public string Names { get; internal set; }
}
internal class Program
{
private static void Main(string[] args)
{
int OrbitalConstant = 10;
List<Asteroid> SolarSystem = new List<Asteroid>()
{
new Asteroid() { Name= "9_Amphitrite" ,StartRoationPeriod=10 ,EndRoationPeriod=50},
new Asteroid() { Name= "24_Themis" ,StartRoationPeriod=45,EndRoationPeriod=100},
new Asteroid() { Name= "259_Aletheia",StartRoationPeriod=40 ,EndRoationPeriod=150},
new Asteroid() { Name= "31_Euphrosyne" ,StartRoationPeriod=60,EndRoationPeriod=200},
new Asteroid() { Name= "511_Davida" ,StartRoationPeriod=195,EndRoationPeriod=250},
new Asteroid() { Name= "87_Sylvia" ,StartRoationPeriod=90,EndRoationPeriod=300},
new Asteroid() { Name= "9_Metis" ,StartRoationPeriod=100,EndRoationPeriod=350},
new Asteroid() { Name= "41_Daphne" ,StartRoationPeriod=110,EndRoationPeriod=400},
};
var result = //I skip the first element because I initialize a new list with that element in the next step
SolarSystem.Skip(1)
//The first argument of Aggregate is a new List with your first element
.Aggregate(new List<AsteroidGroup>() { new AsteroidGroup { Names = SolarSystem[0].Name, EndRoationPeriod = SolarSystem[0].EndRoationPeriod } },
//foreach item in your list this method is called,l=your list and a=the current element
//the method must return a list
(l, a) =>
{
//Now this is your algorithm
//Should be easy to undrestand
var last = l.LastOrDefault();
if (a.StartRoationPeriod + OrbitalConstant >= last.EndRoationPeriod)
{
last.Names += " " + a.Name;
last.EndRoationPeriod = a.EndRoationPeriod;
}
else
l.Add(new AsteroidGroup { Names = a.Name, EndRoationPeriod = a.EndRoationPeriod });
//Return the updated list so it can be used in the next iteration
return l;
});
A more compact solution
var result = SolarSystem
.Skip(1)
.Aggregate( SolarSystem.Take(1).ToList(),
(l, a) => (a.StartRoationPeriod + OrbitalConstant >= l[l.Count - 1].EndRoationPeriod) ?
(l.Take(l.Count - 1)).Concat(new List<Asteroid> { new Asteroid() { Name = l[l.Count - 1].Name += " " + a.Name, EndRoationPeriod = a.EndRoationPeriod } }).ToList() :
l.Concat(new List<Asteroid> { a }).ToList()
);
The following code is taken from the tutorial: http://www.asp.net/mvc/tutorials/getting-started-with-aspnet-mvc3/cs/examining-the-edit-methods-and-edit-view which shows how ASP.net MVC 3 can be used to manage a movie database.
In the tutoral, a list object is added to the controller class that contains every movie genre that exists in the database. This list is then passed to a drop-down in the view enabling the database to be searched by genre.
Controller: (code related to movie genre in bold)
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
What I want to do is enhance this further so that the movies can be searched by price as well as genre. I know I can re-use the much of the same code to do this. I think I need to create a new class that the controller class can pass either the genre or price. Is this correct? IF so, I'd appreciate an example. Thanks.
Update/Clarification:
I want to avoid repeating the code for both genre and price as below:
public ActionResult SearchIndex(string movieGenre, string searchString,float moviePrice)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var PriceLst = new List<string>();
var PriceQry = from d in db.Movies
orderby d.Genre
select d.Genre;
PriceLst.AddRange(GenreQry.Distinct());
ViewBag.moviePrice = new SelectList(PriceLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
if (string.IsNullOrEmpty(moviePrice))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == moviePrice));
}
}
You just have to insert a text box in the view to get price value. Then receive this value at action and modify the query to get desired results.
like this:
#Html.ActionLink("Create New", "Create")
#using (Html.BeginForm()){
<p>Genre: #Html.DropDownList("movieGenre", "All")
Title: #Html.TextBox("SearchString")
Price: #Html.TextBox("Price")
<input type="submit" value="Filter" /></p>
}
And in the action method you are using the code below to populate the dropdownlist with genre values. You need not do the same for price value.
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
And in your action method you just have to use the value of price to filter data
public ActionResult SearchIndex(string movieGenre, string searchString,float price)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where((x => x.Genre == movieGenre) &&(x => x.Price== price)));
}
}
You can do it in so many different ways while all are correct but it depends on the complexity of your project. Basically you don't want to over-engineer a simple program. But in general you should move all of your logic to a separate class and use your actions for creating and calling the right logic class:
public class GetMoviesRequest
{
public string Name { get; set; }
public float? Price { get; set; }
}
public class MoviesLogic
{
private List<Movie> Movies;
public IEnumerable<Movie> Get(GetMoviesRequest request)
{
IEnumerable<Movie> filtered = Movies.AsQueryable();
if (!string.IsNullOrEmpty(request.Name))
{
//Filter by name
filtered = filtered.Where(m => m.Name == request.Name);
}
if (request.Price.HasValue)
{
//Filter by value
filtered = filtered.Where(m => m.Price == request.Price);
}
return filtered;
}
}
public class MyController
{
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var logic = new MoviesLogic();
var movies = logic.Get(new GetMoviesRequest() { Name = searchString } )
///do stuff with movies
}
}
My code is working nicelly but I would like to know if you can suggest something more performatic to get the values frm my dynamic controls.
public class Obj
{
public int ID { get; set; }
public int Quantity { get; set; }
public List<int> getValues (List<Obj> myList)
{
List<Obj> listQuatity = new List<Obj>();
foreach (Obj item in myList)
{
listQuatity.Add(new Obj
{
ID = item.ID,
Quantity = Request.Params["codControl" + item.ID].Trim().Equals("")
? 0
: Convert.ToInt32(Request.Params["codControl" + item.ID])
});
}
}
}
There is no much space for optimization, your loop is pretty simple and straightforward.
One thing I would improve - caching of the Request.Param value instead accessing it twice:
(This could be rewritten using LINQ so would look more elegant but it would not be faster)
public List<int> getValues(List<Obj> myList)
{
List<Obj> listQuatity = new List<Obj>();
foreach (Obj item in myList)
{
// access it once so code would be slightly faster
// but much clear
var requestParam = Request.Params["codControl" + item.ID].Trim();
listQuatity.Add(new Obj
{
ID = item.ID,
Quantity = requestParam.Equals(String.Empty)
? 0
: Convert.ToInt32(requestParam)
});
}
}
Let's see how LINQ query would look:
List<Obj> listQuatity =
myList.Select(i => new {
Id = i.ID,
Param = Request.Params["codControl" + item.ID].Trim(),
})
.Select(i => new Obj {
ID = i.Id,
Quantity = Param.Equals(String.Empty)
? 0
: Convert.ToInt32(i.Param)
})
.ToList();
If you have a simple Linq query like:
var result = from record in db.Customer
select new { Text = record.Name,
Value = record.ID.ToString() };
which is returning an object that can be mapped to a Drop Down List, is it possible to dynamically specify which fields map to Text and Value?
Of course, you could do a big case (switch) statement, then code each Linq query separately but this isn't very elegant. What would be nice would be something like:
(pseudo code)
var myTextField = db.Customer["Name"]; // Could be an enumeration??
var myValueField = db.Customer["ID"]; // Idea: choose the field outside the query
var result = from record in db.Customer
select new { Text = myTextField,
Value = myValueField };
Right way to do this is with closures.
Func<Customer, string> myTextField = (Customer c) => c["Name"];
Func<Customer, int> myValueField = (Customer c) => c["ID"];
var result = from record in db.Customer
select new { Text = myTextField(record),
Value = myValueField(record) };
The one limitation is that your definition of myTextField always needs to return a string.
You could try something like
class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.ID.ToString() },
{ "Name", (Customer c) => c.Name},
{ "Surname", (Customer c) => c.Surname } };
List<Customer> rows = new List<Customer>();
rows.Add(new Customer { ID = 1, Name = "Foo", Surname = "Bar"});
var list = from r in rows
select new { Text = dict["ID"](r), Value = dict["Name"](r) };
To try to access the properties dynamically, you could try something like
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.GetType().GetProperty("ID").GetValue(c,null).ToString() },
{ "Name", (Customer c) => c.GetType().GetProperty("Name").GetValue(c,null).ToString()},
{ "Surname", (Customer c) => c.GetType().GetProperty("Surname").GetValue(c,null).ToString() } };