We have an API with IQueryable<T> resources because we work with OData. My boss requires the resources to be exposed to also have additional information which is not available in the database.
OData allows one to only select the information they need, it does this by mapping a $select=Quantity query parameter to LINQ: performing a .Select(s => s.Quantity). This will of course not work since LINQ to Entities will complain the column does not exist in the database.
public class Item
{
public int Id { get; set; }
public int Quantity
{
get { return SubItems.Count; }
set {}
}
public virtual ICollection<SubItem> SubItems { get; set; }
}
I need a way around this restriction. I have looked into AutoMapper however it fails when using .ProjectTo() with their IValueResolver. I need IValueResolver because some of the extra properties are not so easily mapped onto a SQL structure, for example:
public class Item
{
public int Id { get; set; }
public string Description
{
get { return Convert(DescriptionId); }
set {}
}
public int DescriptionId { get; set; }
}
public string Convert(int value)
{
switch(value)
{
case 1:
return "1";
default:
return "0";
}
}
I have looked into the code below as well but this fails with System.ArgumentException: 'Cannot apply ODataQueryOptions of 'DtoItem' to IQueryable of 'Item'. Parameter name: query'. Reversing the data types I manage to get a result but I cannot return this of course as the data type is not matching with my controller function. I do however need the ODataQueryOptions of DtoItem otherwise I will not have access to $select any of those extra fields because it will not know about them. But then I am back to square one on how would OData even apply those options to an item of a different type.
public IQueryable<Item> Get(ODataQueryOptions queryOptions)
{
IQueryable<Item> items = (IQueryable<Item>) queryOptions.ApplyTo(repositoryItem.Select());
return mapper.Map<List<DtoItem>>(items.ToList()).AsQueryable(); // Cannot convert DtoItem to Item but of course I want to return DtoItem
}
What is necessary for me to accomplish this requirement? I have looked into Entity Framework but it seems very much sealed of. I'm not even sure if modifying the expression tree would be enough. I'd still have to add the values somehow during materialization.
Related
Suppose I have a model called Item which looks like this:
public int ID { get; set; }
public DateTime DateCreated { get; set; }
public DateTime? DateArchived { get; set; }
[Required]
public string Alias { get; set; }
It has been included in the DbContext...
public DbSet<Item> Items { get; set; }
Whenever I want to select all items from the database, I want to include only items where Archived == null and sorted by Alias.
Is there a way to set up EF to do this when the DbSet is called (eg. on the DbContext)?
For example, if I call db.Items in a controller, I always want these options applied without having to state them explicitly.
My current way to do this is to rename the DbSet with prefix "All" and add a function with these options applied (function uses DbSet's original name so that scaffold controllers and pages use the customized query without requiring any changes):
public DbSet<Item> AllItems { get; set; }
public IQueryable<Item> Items
{
get { return AllItems.Where(item => item.Archived == null).OrderBy(item => item.Alias); }
}
But something about this method feels hackish/wrong. Is this a fine way to do it? How should this typically done?
I've had similiar problem once and I created a database view to present my data with all neccessary limitations. View can have the same fields as a table, but not required to. Then you create another DbSet item, e.g. DbSet<ItemView>, which in the database coresponds to view instead of table. Inside of this view you declare where statement as you wish. If properties are the same, you can also create the same ancestor for both classes (e.g. Item and ItemView both inherit from ItemBase).
I've had this done on MS SQL Server.
I generally avoid accessing DbSet's directly and enforce the idea by hiding the context behind an interface. Then I only access repository methods from the interface. This hides the DbSets from intelli-sense when using it in your client code if you'd prefer client code doesn't have access to the table collections (and want to steer them to some filtered subset of data).
public interface IDataContext : IDisposable
{
void SaveChanges();
IQueryable<Item> GetItems();
}
public class DataContext : DbContext, IDataContext
{
public DataContext(string connectionStringName) : base(connectionStringName) { }
public DbSet<Item> Items { get; set; }
public IQueryable<Item> GetItems()
{
return Items.Where(item => item.Archived == null).OrderBy(item => item.Alias);
}
}
using (IDataContext context = new DataContext("myConnection"))
{
var items = context.GetItems();
}
I'm using EF 4 for the first time in a WebAPI service. I understand the basics if EF, but its still a bit of a learning curve when I'm used to using ADO.
I only want to return a subset (5) of over 100 fields in the table.
Model (EF Generated):
public partial class ITEM_MASTER
{
public string IM_ITEM_CODE { get; set; }
public string IM_UPC { get; set; }
public string IM_SUBDEPARTMENT { get; set; }
public string IM_DESC { get; set; }
...
}
Get Method in my Controller:
// GET api/Products
public IQueryable<ITEM_MASTER> GetProduct()
{
// return db.ITEM_MASTER;
return db.ITEM_MASTER.Select(x => new ProductList { ItemCode = x.IM_ITEM_CODE });
}
The return db.ITEM_MASTER works, but with the .Select method I get the error:
Cannot implicitly convert type
'System.Linq.IQueryable<JWebAPI.Models.ProductList>' to
'System.Linq.IQueryable<JWebAPI.Models.ITEM_MASTER>'. An explicit
conversion exists (are you missing a cast?)
public class ProductList
{
public string ItemCode {get; set;}
}
The ProductList class acts as a place holder object, as using the .Select returns a DBQuery not the original object type. I read that this was the ideal way to handle this situation. I'll add more properties to it when I've got it working. I have a downloaded sample that is structured the same way, and that works.
Is this the best way do do what I need, return only a subset of the EF fields? How can i resolve the conversion error?
Well the error is quite clear, your method is suppose to return IQueryable<ITEM_MASTER> but you are trying to return 'System.Linq.IQueryable<JWebAPI.Models.ProductList>'.
Since you projected your query's result to 'System.Linq.IQueryable<JWebAPI.Models.ProductList>' You need to have that as return type.
// GET api/Products
public IQueryable<ProductList> GetProduct() //Change return type
{
// return db.ITEM_MASTER;
return db.ITEM_MASTER.Select(x => new ProductList { ItemCode = x.IM_ITEM_CODE });
}
Since you are selecting only a subset of your original entity, you can't project to entity mapped to a table in Entity framework, that is why you need a place holder class ProductList
First, a little background. I'm developing a REST API using ASP.NET Web API and Entity Framework 5 however the requirements of the system are such that several layers of logic sit between my ApiControllers and my DbContext. These layers of logic involve detaching my entities from the DbContext, applying sets of hypothetical changes to the entities in memory (a process I'm calling materialization of a change set) then allowing users to inspect the new state of the system should these changes get applied. The new state of the entities is not saved to the database immediately. Instead, the materialization is held in memory on the web server and users can inspect either the current data or one of the many materialization of a variety of change sets.
Now for my problem.
public interface IIdentifiable
{
long Id { get; set; }
}
public class Foo : IIdentifiable
{
public long Id { get; set; }
public string Name { get; set; }
public List<Bar> Bars { get; set; } // Navigation Property
}
public class Bar : IIdentifiable
{
public long Id { get; set; }
public string Name { get; set; }
public long FooId { get; set; } // Foreign Key Property
public Foo Foo { get; set; } // Navigation Property
}
public class Materialization
{
IEnumerable<Foo> Foos { get; set; }
IEnumerable<Bar> Bars { get; set; }
}
public interface IRepository<TItem> : IQueryable<TItem>, ICollection<TItem>, IDisposable
where TItem : class, IIdentifiable
{
IRepository<TItem> Include<TProperty>(Expression<Func<TItem, TProperty>> path);
// Other methods
}
public class MateriailizationRepository<TItem> : IRepository<TItem>
where TItem : class, IIdentifiable
{
private Materialization _materialization;
public MateriailizationRepository(Materialization materialization)
{
_materialization = materialization;
}
public IRepository<TItem> Include<TProperty>(Expression<Func<TItem, TProperty>> path)
{
// Populate navigation property indicated by "path"
}
// Other methods
}
Each Bar has a foreign key property indicating the Foo it belongs to but the Bar.Foo and Foo.Bars navigation properties are not populated as this would complicate with the materialization process. Hence, after materialization has completed, Materialization.Foos and Materialization.Bars contain collections of objects that refer to each other by foreign key properties but not by navigation properties (i.e. the values of all navigation properties are null or empty List<T>s). I want to be able to do something like the following in my ApiController.
public IQueryable<Foo> Get (bool includeBars = false)
{
Materialization materialization;
// Materialize
using (IRepository<Foo> repository = new MateriailizationRepository<Foo>(materialization))
{
IRepository<Foo> query = repository;
if (includeBars)
query = query.Include(f => f.Bars);
return query;
}
}
MateriailizationRepository<Foo>'s primary responsibility is to fetch materialized Foo objects but since it has a reference to the entire Materialization I would like to be able to include materialized Bar objects from Materiailization.Bars on demand.
How would I go about implementing MateriailizationRepository.Include() to mimic the IQueryable.Include() extension method?
Here are a couple of options:
Look at using another context to implement your MaterializationRepositories and have it backed by an in memory database such as Effort, if that's still working nowadays.
Re-implement the 'Include' functionality yourself on the Materialization. The Expression can be broken down to find the type of the navigation property. Using naming conventions you can work out what foreign key property you need to interrogate to get the correct identifier. To find the target repository you could use reflection over the Materialization looking for the public property of type IEnumerable of the type of the navigation property. As long as you knew the name of the primary key of the target entity (by convention, say) you could then use the foreign key value to find it.
If you have a small number of entity types you'd probably be better off having some kind of switch statement and do some of it manually rather than via reflection.
Apologies that this isn't a fully worked through implementation, but I hope it leads in the right direction.
I am new to EF. I am trying to get Entity Framework 4.2 to do a sort by a calculated property (not mapped).
Here is what my entity look like:
public class Site : Entity
{
public Site()
{
Equipments = new HashSet<Equipment>();
Forecasts = new HashSet<Forecast>();
}
[StringLength(8)]
public string Number { get; set; }
[StringLength(50)]
public string EquipmentShortCLLI { get; set; }
[StringLength(50)]
public string Location { get; set; }
public virtual Central Central { get; set; }
public virtual ICollection<Equipment> Equipments { get; set; }
public virtual ICollection<Forecast> Forecasts { get; set; }
#region Calculated Items
public bool IsEmbargo {
get { return Equipments.Count > 0 && Equipments.SelectMany(x => x.EquipmentDetails).Any(e => e.IsEmbargo); }
}
//...
public int PortsCapacity
{
get
{
return Equipments.Count > 0
? Equipments.SelectMany(x => x.Slots).Sum(x => x.PortsCapacity)
: 0;
}
}
#endregion
//...
By trying to order using any of my readonly properties I am getting the exception:
The specified type member 'PortsCapacity' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Which makes sense because EF is trying to build an sql orderby with a field that does not exist in the database (my understanding..).
Now, by using some dynamic linq code I was able to make this work for my many-to-one columns by passing "Central.SomeField" (as opposed to making a ReadOnly Property that returns Central.SomeField).
I.E.:
query.OrderBy("Central.SomeField");
However, I still face the same issue when it comes to a collection of items (Equipments). I am trying to make this as dynamic as possible by using a string coming from the client side and avoiding a long switch case, but at this point I will accept any ideas, so long as the sorting happens on the database side.
Edit 1:
Following what Ladislav Mrnka says, how would one execute an OrderBy clause on one-to-many child items using lambdas or expression?
I don't think that Dynamic Linq is capable of this. You need a real Linq subquery to compute aggregations on Equipements so it will simply not work. If the user selects ordering by IsEmbargo or PortsCapacity you must have some switch / if block to handle this case by appending special part of the query - no other way.
I have a POCO domain model which is wired up to the entity framework using the new ObjectContext class.
public class Product
{
private ICollection<Photo> _photos;
public Product()
{
_photos = new Collection<Photo>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Photo> Photos
{
get
{
return _photos;
}
}
public void AddPhoto(Photo photo)
{
//Some biz logic
//...
_photos.Add(photo);
}
}
In the above example i have set the Photos collection type to IEnumerable as this will make it read only. The only way to add/remove photos is through the public methods.
The problem with this is that the Entity Framework cannot load the Photo entities into the IEnumerable collection as it's not of type ICollection.
By changing the type to ICollection will allow callers to call the Add mentod on the collection itself which is not good.
What are my options?
Edit:
I could refactor the code so it does not expose a public property for Photos:
public class Product
{
public Product()
{
Photos = new Collection<Photo>();
}
public int Id { get; set; }
public string Name { get; set; }
private Collection<Photo> Photos {get; set; }
public IEnumerable<Photo> GetPhotos()
{
return Photos;
}
public void AddPhoto(Photo photo)
{
//Some biz logic
//...
Photos.Add(photo);
}
}
And use the GetPhotos() to return the collection. The other problem with the approach is that I will loose the change tracking abilities as I cannot mark the collection as Virtual - It is not possible to mark a property as private virtual.
In NHibernate I believe it's possible to map the proxy class to the private collection via configuration. I hope that this will become a feature of EF4. Currently i don't like the inability to have any control over the collection!
The way to do this is to have a protected virtual property which is mapped in your model and a public property that returns an IEnumerable.
public class Product
{
public Product()
{
PhotoCollection = new Collcation<Photo>();
}
public int Id { get; set; }
public string Name { get; set; }
protected virtual ICollection<Photo> PhotoCollection {get; set; }
public IEnumerable<Photo> Photos
{
get { return PhotoCollection ; }
}
public void AddPhoto(Photo photo)
{
//Some biz logic
//...
PhotoCollection .Add(photo);
}
}
Anton, it would help me understand your problem more if you can explain why is it that you do not want developers to access the Add method of your collection. Is this because the list is strictly read-only, or is it because you want to run some custom business logic when a new entity is added?
Anyway... I am going to assume that you are trying to do the latter (i.e. run custom business logic when the collection is modified). I have done a similar solution on a project of mine, and the idea is as follows:
The TT template that produces POCOs in EF4 creates all collections as TrackableCollection lists. This class has an event called 'CollectionChanged' which you can subscribe to and listen to any changes to your collection.
So you can do something as follows:
public class Product
{
public Product()
{
Photos.CollectionChanged += ListCollectionChanged;
}
public int Id { get; set; }
public string Name { get; set; }
public TrackableCollection<Photo> Photos
{
get
{
// default code generated by EF4 TT
}
set
{
// default code generated by EF4 TT
}
}
private void ListCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
// A new item has been added to collection
case NotifyCollectionChangedAction.Add:
{
T newItem = (T) e.NewItems[0];
// Run custom business logic
}
break;
// An existing item has been removed
case NotifyCollectionChangedAction.Remove:
{
T oldItem = (T) e.OldItems[0];
// Run custom business logic
}
break;
}
}
}
The nice thing about the above solution is that you still use your Product entity in an 'EF' manner... were any developer in your team can simply access a property of the entity directory and need run an explicit hard typed function.
Bit late to the party but this is what Observable objects are for. Allow the data structure to do what it does best. Use ObservableCollection as your field type if you don't want to build your own collection that does what you need and expose the regular ICollection type from your property. You can run any logic in the parent entity you need when the related entities in the collection change via the CollectionChanged event. If you need to selectively enable or disable modifications it's easy enough to extend an existing collection type or write a proxy collection that allows a call to a method to toggle the mutability of the collection (ISupportInitialize can be used to good effect for representing this ability BTW).
(Apologies for my initial post brevity - I was answering from my phone)
You can construct your collection through a LINQ query over an EF entity set. However, you keep the resulting collection as internal data member to your business class and expose the IEnumerable<Photo> returned by calling AsEnumerable() on the entity set as a result of the public photo.
You could cache the IEnumerable<Photos> internally as well, so that you don't call AsEnumerable() every time your caller asks for the collection. Of course, that means that if the user needs to update the collection through your public methods, you might have to refresh the cached IEnumerable. This might pose small issue if the caller has also cached the pointer to the previous IEnumerable.
Alternatively, if your caller will always work with the full entity set, the EntitySet class (of which all your EF sets will inherit) implements IEnumerable<TEntity>, so you can directly return the entity set to your caller.
Note that if you want the loading of the collection from an EF entity set to happen outside of the scope of your business class, you can make a constructor on your class that takes an ICollection. This way, once you create your object, the collection is sealed in it, and exposed only as an IEnumerable.
Why not try the following and leave use properties?
private ICollection<Photo> photos{get; set;}
public IEnumerable<Photo> Photos
{
get {return (IEnumberable<Photo>)photos;}
}
Alternatively you could use the decorator pattern to encapsulate the class into one which the collection can't be directly modified.