I'd like to pass a function to LINQ queries to do operations on existing data. For eg. I have this function:
string doStuff(Product prod)
{
return string.Format("{0} - {1}", prod.f1, prod.s2);
}
When requesting products, i'd like to pass in this function so it returns this string and assign it to a product property NewString.
An example of the POCO object the repository returns:
class Product
{
public string NewString{get; set;};
public string f1 {get; set;};
public string s2 {get; set;};
}
//Service layer
public IList<Product> GetProducts()
{
//I currently have this:
return _repository.GetProducts().ToList();
//OR, is something like this possible?
return _repository.GetProducts().Select(p => p.NewString = doStuff(p));
}
All methods in the repository returns IQuerable<Product>
So basically, I want to generate a new string from existing data from the service layer. How do I accomplish this without looping through the returned list of objects?
If it is calculated based on Product fields and is needed where Product is needed, why not just add this function call to the NewString getter?
//Service layer
public IList<Product> GetProducts()
{
return _repository
.GetProducts()
.Select(p => new Product {
NewString = doStuff(p),
f1 = p.f1,
s2 = p.s2
})
.ToList;
}
Related
I'm trying to map the response from an SP to corresponding models, custom for the specific SP. The SP uses column names that I don't want to see anywhere in my code because they're violating all standards (even bad spelling).
So, I tried creating a model like this one.
public sealed class Product
{
[Column("Id_PRODUCT")]
public int Id {get; set;}
[Column("PruductTilte")]
public string Title {get; set;}
// Column Name = Description
public string Description {get; set;}
}
And then in the partial Context
public partial class ContosoContext
{
public async Task<Product[]> GetProductsAsync()
{
using (var cmd = Database.Connection.CreateCommand())
{
cmd.CommandText = "GeTpRoDuCtS";
using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
{
return ((IObjectContextAdapter)this)
.ObjectContext
.Translate<Product>(reader)
.ToArray();
// Actually calling reader.NextResult() and doing more translates here (multi-set response).
}
}
}
}
When executing this like var products = await new Context().GetProductsAsync() only Description is set, EF6 seems to be ignoring the ColumnAttribute.
Isn't this supposed to work? Is there any good work-around?
I try to Lazy< SelectList > for lazy caching any lookup data in my ASP.NET MVC project. But I cannot force Lazy object to reload lookup data when it is changed.
I create derived class like the following code. I found that Lazy< T > use IsValueCreated property to keep current state. However, in MappingFunc method I cannot change value of IsValueCreated because it is static method.
public class LazySelectList : Lazy<SelectList>
{
public LazySelectList(Func<LimeEntities, IEnumerable> initFn, string dataValueField, string dataTextField)
: base(MapingFunc(initFn, dataValueField, dataTextField))
{
}
public new bool IsValueCreated { get; set; }
public static Func<SelectList> MapingFunc(Func<DbContext, IEnumerable> valueFactory, string dataValueField, string dataTextField)
{
return () =>
{
var context = ObjectFactory.GetInstance<DbContext>();
return new SelectList(valueFactory(context), dataValueField, dataTextField);
};
}
}
I use the below code the call this function. But it always creates new value because IsValueCreated value is always false.
LookupCache.DocTypeList = new LazySelectList(db => db.DocTypes.OrderBy(x => x.Name), "ID", "Name");
After a several hours for searching & testing, I think it is impossible to reset state of lazy object. But I can create wrapper for handling this problem. The wrapper class contains lazy object and necessary object for creating new lazy object. The code should be like this.
public class LazyCache<TSource, TModel> : IValue<TModel>
{
private Lazy<TModel> _lazyObj;
private readonly Func<TSource, TModel> _valueFactory;
protected LazyCache()
{
Reset();
}
public LazyCache(Func<TSource, TModel> valueFactory) : this()
{
_valueFactory = valueFactory;
}
public void Reset()
{
_lazyObj = new Lazy<TModel>(MapingFunc());
}
public TModel Value
{
get { return _lazyObj.Value; }
}
protected virtual Func<TModel> MapingFunc()
{
return () =>
{
var context = ObjectFactory.GetInstance<TSource>();
return _valueFactory(context);
};
}
}
The above code allows us to reset state of object to force it to retrieve new data for defined function.
After that, I try to use the above method to cache SelectList object in ASP.NET MVC. But it always retrieves new from database because SelectList will contain IEnumerable object instead of real object data. So, I solve problem by enumerating data into temp object list like the following class.
public class LazyList<TSource> : LazyCache<TSource, SelectList>
{
private readonly Func<TSource, IEnumerable> _valueFactory;
private readonly string _dataValueField;
private readonly string _dataTextField;
public LazyList(Func<TSource, IEnumerable> valueFactory, string dataValueField, string dataTextField)
{
_valueFactory = valueFactory;
_dataValueField = dataValueField;
_dataTextField = dataTextField;
}
protected override Func<SelectList> MapingFunc()
{
return () =>
{
var context = ObjectFactory.GetInstance<TSource>();
// Force to retrieve data from current IEnumerable to prevent lazy loading data that
// cause system always connect to database every time they generate data from selectlist.
var loop = _valueFactory(context).GetEnumerator();
var tempList = new List<object>();
while (loop.MoveNext())
{
tempList.Add(loop.Current);
}
return new SelectList(tempList, _dataValueField, _dataTextField);
};
}
}
PS. All source code are a part of my Higgs RIA framework that available on Codeplex website.
LazyCache.cs | LazyList.cs
I'm using code first and trying to do a simple query, on a List property to see if it contains a string in the filtering list. However I am running into problems. For simplicity assume the following.
public class Person
{
public List<string> FavoriteColors { get; set; }
}
//Now some code. Create and add to DbContext
var person = new Person{ FavoriteColors = new List<string>{ "Green", "Blue"} };
dbContext.Persons.Add(person);
myDataBaseContext.SaveChanges();
//Build
var filterBy = new List<string>{ "Purple", "Green" };
var matches = dbContext.Persons.AsQueryable();
matches = from p in matches
from color in p.FavoriteColors
where filterBy.Contains(color)
select p;
The option I am considering is transforming this to a json serialized string since I can perform a Contains call if FavoriteColors is a string. Alternatively, I can go overboard and create a "Color" entity but thats fairly heavy weight. Unfortunately enums are also not supported.
I think the problem is not the collection, but the reference to matches.
var matches = dbContext.Persons.AsQueryable();
matches = from p in matches
from color in p.FavoriteColors
where filterBy.Contains(color)
select p;
If you check out the Known Issues and Considerations for EF4 this is more or less exactly the case mentioned.
Referencing a non-scalar variables,
such as an entity, in a query is not
supported. When such a query executes,
a NotSupportedException exception is
thrown with a message that states
"Unable to create a constant value of
type EntityType.
Also note that it specifically says that referencing a collection of scalar variables is supported (that's new in EF 4 imo).
Having said that the following should work (can't try it out right now):
matches = from p in dbContext.Persons
from color in p.FavoriteColors
where filterBy.Contains(color)
select p;
I decided to experiment by creating a "StringEntity" class to overcome this limitation, and used implicit operators to make nice easy transformations to and from strings. See below for solution:
public class MyClass
{
[Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
public Guid Id { get; set; }
public List<StringEntity> Animals { get; set; }
public MyClass()
{
List<StringEntity> Animals = List<StringEntity>();
}
}
public class StringEntity
{
[Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
public Guid Id { get; set; }
public string Value { get; set; }
public StringEntity(string value) { Value = value; }
public static implicit operator string(StringEntity se) { return se.Value; }
public static implicit operator StringEntity(string value) { return new StringEntity(value); }
}
public class MyDbContext : DbContext
{
public DbSet<MyClass> MyClasses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyClass>()
.HasMany(x => x.Animals)
.WithMany()
.Map(x =>
{
x.MapLeftKey(l => l.Id, "MyClassId");
x.MapRightKey(r => r.Id, "StringEntityId");
});
}
}
...Everything looked like it was working perfectly with some testing(Albeit heavy), and then I implemented for its original purpose, a Multiselect ListBox in an MVC3 view. For reasons unknown to me, IF the ListBox is assigned the same NAME as an Entity Collection Property, none of your selected items will be loaded.
To demonstrate the following did NOT work:
//Razor View Code
string[] animalOptions = new string[] {"Dog", "Cat", "Goat"};
string[] animalSelections = new string[] {"Dog", "Cat"};
Html.ListBox("Animals", Multiselect(animalOptions, animalSelections));
To get around this limitation, I needed to do four things:
//#1 Unpluralize the ListBox name so that is doesn't match the name Model.Animals
var animalOptions = new string[] {"Dog", "Cat", "Goat"};
#Html.ListBox("Animal", new MultiSelectList(animalOptions, Model.Animals.Select(x => x.Value)))
//#2 Use JQuery to replace the id and name attribute, so that binding can occur on the form post
<script type="text/javascript">
jQuery(function ($) {
$("select#Animal").attr("name", "Animals").attr("id", "Animals");
});
</script>
//#3 Create a model binder class to handle List<StringEntity> objects
public class StringEntityListBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringArray = controllerContext.HttpContext.Request.Params.GetValues(bindingContext.ModelName);
return stringArray.Select(x => new StringEntity(x)).ToList();
}
}
//#4 Initialize the binder in your Global.asax setup.
ModelBinders.Binders.Add(typeof(List<StringEntity>), new StringEntityListBinder ());
Note, that the Listbox bug did NOT occur when the property was a List of strings, it just didn't like it when it was a List of entities.
I am trying to post from a complex object from data model, a Json, unfortunately, I don’t want to pass all the hierarchy (person->HasMany Orders/ Orders HasMany Products etc.) but only the “first level” (for example person names for a grid view).
public JsonResult Search(string fMname, string fSname)
{
IList<Person> people = personRepository.FindAllMatchingName(fMname, fSname);
//Here with lazy loading querying only the “first level” for object
var data = people;
return Json(new { items = data });
//Here querying full object hierarchy and return the big Json
}
I am looking for a solution to filter the Json object and – if this is possible – to work the lazy loading and to avoid the sql overhead.
Any ideas?
Create a simplified person class that only contains the properties you need. Then transform the IList of Person into a list of the simplified type using Linq.
public class SimplePerson
public string FirstName { get; set; }
public string LastName { get; set; }
}
public JsonResult Search(string fMname, string fSname)
{
IList<Person> people = personRepository.FindAllMatchingName(fMname, fSname);
var data = people.Select(m => new SimplePerson() { FirstName = m.FirstName, LastName = m.LastName }).ToList();
return Json(new { items = data });
}
You could use an anonymous type instead, but it wouldn't be strongly typed in the view.
I would like to use Linq and strongly typed views in the right way. at the moment I do the following:
Make a Model to verify agianst:
public class Menu
{
public int Id { get; private set; }
public string Text { get; private set; }
public string Action { get; private set; }
public string Controller { get; private set; }
public string Parameter { get; private set; }
public string Langue { get; private set; }
public Menu(int id, string controller, string action, string parameter, string text)
{
Id = id;
Controller = controller;
Action = action;
Text = text;
Parameter = parameter;
}
Use Linq to get the data from the database into the model:
public static List<Menu> GetTabListForMenu(string langue)
{
Page_dbEntities entity = new Page_dbEntities();
var tabList = (from ml in entity.wpmenulangue
where ml.Langue == langue
from m in entity.wpmenu
where ml.Menu == m.Id
from l in entity.wplangue
where ml.Langue == l.Langue
from p in entity.wppage
where p.Id == m.Page
select new { m.Id, p.Controller, p.Action, p.Parameter, ml.Text}).ToList();
List<Menu> menu = new List<Menu>();
foreach (var item in tabList)
{
menu.Add(new Menu(item.Id, item.Controller, item.Action, item.Parameter, item.Text));
}
return menu;
}
I am pretty convinced that this is not the optimal way to do this and have 2 questions:
When I get the data from the database I first use a var and then have to move it to the object with a foreach. this seems like a waste of both my time and less effeicent then getting it with sql.
I have been told that I can just verify up agianst the entitymodel. Even if i use multiple entities in a view. is this true? (the one telling me this wes not able to get it to work and I have not been able to find anything about it online).
I will try to look back on this post in the next couple of hours, but might have to wait 24 hours.
public static List<Menu> GetTabListForMenu(string langue)
{
Page_dbEntities entity = new Page_dbEntities();
return (from ml in entity.wpmenulangue
where ml.Langue == langue
from m in entity.wpmenu
where ml.Menu == m.Id
from l in entity.wplangue
where ml.Langue == l.Langue
from p in entity.wppage
where p.Id == m.Page
select new Menu(m.Id, p.Controller, p.Action, p.Parameter, ml.Text)
).ToList();
}
As for the validation is concerned you shouldn't use multiple entities in the view. You should use a single entity which is called ViewModel. This ViewModel is a class that represents the data on the view. If you are using DataAnnotations for validation you could decorate this view model properties with attributes that indicate how to be validated.