I'm using Entity Framework with a code-first approach to store data for a C# application I'm working with a SQL Server database. A challenge I'm currently running into involves a structure (approximately) like this:
public class MainEntity
{
// Data
public List<SubEntity> SubEntities { get; private set; }
}
public class SubEntity
{
// More Data
bool DoNotLoad { get; set; }
}
Now, I know that Entity Framework is able to "see" private property setters and populate the entities using reflection. That's why this works:
IEnumerable<MainEntity> Entities = MainEntities.Include(m => m.SubEntities).ToList();
And it will retrieve the MainEntity and all of its SubEntities from the database even though the setter for SubEntities is private.
I also know that Entity Framework supports more free-form projections, like so:
var projectedEntities = MainEntities.Select(m =>
new {
Main = m,
Sub = m.SubEntities.Where(s => !s.DoNotLoad)
}
);
And then I'll have an anonymous type with the main entity and its sub entities, with a filter applied to the sub entities.
However, I would like to combine the two methods and end up with MainEntity objects that have their SubEntity property populated, but filtered.
Unfortunately, this doesn't work:
var invalidEntities = MainEntities.Select(m =>
new MainEntity{
SubEntities = m.SubEntities.Where(s => !s.DoNotLoad)
}
);
C# doesn't let me use property initialization that way because SubEntities has a private setter, even though Entity Framework would work around that. Is there a way to make this work how I want it? My first priority is to avoid making two queries (e.g. get MainEntity, get filtered SubEntities, use specialized code to insert it), but I would also like to actually do the filtering in the database rather than getting everything and then filtering locally (e.g. MainEntity.FilterSubEntities()). Making the setter public isn't entirely impossible, but in order to use Property initialization I think I would need to change EVERY setter to public, which I would rather avoid.
I've been told that this is possible by projecting into an Anonymous type and if I name things in a certain way Entity Framework will "recognize" that it should project into MainEntity instead, but I haven't been able to find any references to this anywhere else. If that is possible then that would be my preferred method since it seems flexible enough to apply in various other situations where I need to filter in other ways.
I've found it
I've finally found an example of what I was told, and my testing indicates that it works. A few notes about the method:
It does not work to arbitrarily project into private properties or properties with private setters. It only allows for more complex filtering of sub-entities, which fortunately was the primary use I was looking for.
This may be undocumented behavior, or abuse of some other feature, so for all I know it could break at any time, and I can't necessarily state what would happen in any edge-cases that arise.
I don't have any evidence that this is better by any metric than simply filtering locally, and it could be significantly worse. I haven't measured it, and I recognize that this seems to be "odd behavior" that might mess with Entity Framework's normal optimizations. I think it's pretty cool though.
With that out of the way, this is the technique using the sample classes described in the question:
var queryResult = MainEntities.Select(m =>
new {
MainEntity = m,
m.SubEntities.Where(s => !s.DoNotLoad)
})
.ToList();
var finalList = queryResult.Select(q => q.MainEntity).ToList();
(Doing this on two lines and using a temporary variable isn't strictly necessary, but I think it clarifies that a DB query is executed at the first ToList() and then additional operations are applied locally.)
I believe that this works because Entity Framework populates navigation properties in a particularly eager manner. Essentially it populates the SubEntities List purely by adding every loaded SubEntity that has a foreign key to that MainEntity, regardless of what caused those entities to be loaded. That is speculation though, all I definitely know is that it currently works how I need it to.
Related
I'm not sure how to best make this work for code-first entity-framework. I'd want to make sure that any changes to the list would modify the csv property. I would also want to make sure that any linq2sql queries would still work efficiently(-ish) using this approach.
The Goal:
public class Profile {
public string Email {get;set;}
// This has to save to the DB somehow.
public IEnumerable<int> EmailAudienceTypes { get; set; }
}
Context:
Alright, ignore the obvious normalization problems you could complain about for this. In our company we have an AudienceType table that functions as a kind of enum. You pick one type from the list and store the chosen type using the type's id. Everything from email addresses to user activities can be controlled with a AudienceTypeID. In the past we would have "EmailAudienceTypeId" as a property to mark the level of sharing to be used for that field. So that's the legacy code.
We have a chance for site refactor. They'd like to be able to select multiple audiences for each attribute now. Normalized solution would be terrible.
So I've got a kind of wacky solution in mind, but I'm not sure how to best make this work for code-first entity-framework. I'd want to make sure that any changes to the list would modify the csv property. I would also want to make sure that any linq2sql queries would still work efficiently(-ish) using this approach.
public List<int> EmailAudienceTypes {
get {
return EmailVisibleToRolesCsv.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => int.Parse(s))
.ToList();
} }
I'm using EF 6 to work with a somewhat shoddily constructed database. I'm using a code-first model.
A lot of the logical relations there aren't implemented correctly using keys, but use various other strategies (Such as character-separated ids or strings, for example) that were previously manipulated using complex SQL queries.
(Changing the schema is not an option)
I really want to capture those relations as properties. It's possible to do this by using explicit queries instead of defining actual relations using the fluent/attribute syntax.
I'm planning to do this by having IQueryable<T> properties that perform a query. For example:
partial class Product {
public IQueryable<tblCategory> SubCategories {
get {
//SubCategoriesID is a string like "1234, 12351, 12" containing a list of IDs.
var ids = SubCategoriesID.Split(',').Select(x => int.Parse(x.Trim()));
return from category in this.GetContext().tblCategories
where ids.Contains(category.CategoryID)
select category;
}
}
}
(The GetContext() method is an extension method that somehow acquires an appropriate DbContext)
However, is there a better way to do this that I'm not familiar with?
Furthermore, if I do do this, what's the best way of getting the DbContext for the operation? It could be:
Just create a new one. I'm a bit leery of doing this, since I don't know much about how they work.
Use some tricks to get the context that was used to create this specific instance.
Do something else?
First, I would recommend not returning an IQueryable, as that retains a relationship to the original DbContext. Instead, I'd ToList the results of the query and return that as an IEnumerable<tblCategory>
Try not to keep DbContext instances hanging around; there's a lot of state management baked into them, and since they are not thread-safe you don't want to have multiple threads hitting the same instance. The pattern I personally tend to follow on data access methods is to use a new DbContext in a using block:
using (var ctx = new YourDbContextTypeHere()) {
return (from category in ctx.tblCategories
where ids.Contains(category.CategoryID)
select category).ToList();
}
Beware that .Contains() on a list of ids is very slow in EF, i.e. try to avoid it. I'd use subqueries, such as
var subcategories = context.SubCategories.Where(...);
var categories = context.Categories.Where(x => subCategories.Select(x => x.Id).Contains(category.CategoryId);
In this setup, you can avoid loading all the ids onto the server, and the query will be fast.
I have an object, say the usual Order that have a collection of LineItem. To satisfy some business rules I need to have complete control against the list (i.e. nobody can add an element to this list simply using the built in Add() method). So I do something like:
public class Order {
private List<LineItem> lineItems {get; set;}
public IEnumerable<LineItem> LineItems {
get { return lineItems.AsReadOnly(); }
}
}
This solution do the job but have a big side effects if other player come into play (e.g. Entity Framework). In fact during the model building, EF expects an ICollection (while I need an IEnumerable):
modelBuilder.Entity<Order>()
.HasMany<LineItem>(e => e.LineItems) // here is the exception: No implicit conversion among IEnumerable and ICollection
.OtherMappingProperties(); // <-- this don't belong to EF's Fluent API :)
EDIT 1 - The solution provided by #Pieter21 avoid the compile time error, since LineItem is now a Colection, but I get a runtime exception when somewhere in my code I have something like:
return Set.Include(s => s.LineItems)
since none have set lineItems (none could since is private) so I get a NullPointerException.
Since the Entity configuration and Domain model are in different package the use of internal instead of private would not resolve the problem unless the use of InternalsVisibleTo (but I don't want to spoil the AssemblyInfo.cs of the Domain model with something persistence related)
How to workaround this issue?
PS: The EF configurations and domain model are in different namespaces.
Can you do something like:
public ICollection<LineItem> LineItems
{
get
{
return new ReadOnlyCollection<LineItem>(lineItems);
}
}
In the data architecture I have to contend with, there are no deletes. Instead, all records have a nullable datetime2 that signals the record has been "disabled". This means that in the cases of direct selections on the entities, I'll always have to add in a check to see if the entity was disabled or not.
So far what I've come up with is just a simple extension method called .Enabled() that gets only the enabled rows. It seems effective so far, but it's also annoying that I have to type that in every case.
Surely someone else has skinned this cat before. Is there a better way to do this with Entity Framework I'm just not privy to?
I suppose you could do something like this
public class MyContext : DbContext
{
public IDbSet<Thing> Things { get; set; }
public IQueryable<Thing> EnabledThings
{
get
{
return Things.Where(t => t.Enabled);
}
}
}
or the same as an extension method (but on the context not the DbSet/queriable).
Personally in practice I actually do exactly as you have in your example and use Things.Enabled().Whatever
I don't know of anything native to Entity Framework. But normally when I run into this, I create an "repository" layer that I run most database transactions through. Then I create methods like GetAll() which returns all items with appropriate where statement in place to hide "deleted" items.
I have a LINQ to Entities query (using EF 4) that has some pretty complicated set-based filtering going on. The code compiles just fine, but when I try to run it, I get the following error:
Unable to create a constant value of type 'ITextEntity'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
Now for the code. I have an interface that looks like this:
public interface ITextEntity
{
int ID { get; set; }
string TextValue { get; set; }
EntityCollection<Product> Products { get; set; }
}
The idea is that these "Text Entities" represent lookup tables storing properties of Product. If Products have a color, all red Products will contain a Color entity with TextValue = "Red", and there will be a Color entity decorated with this interface:
public partial class Color : ITextEntity
{
//ID, TextValue, and Products implemented in EF-generated code
}
ITextEntities may have 1:N or N:N relationships back to Product.
I have a nested collection (actually a List<IEnumerable<ITextEntity>>) which contains sets of different entities implementing the ITextEntity interface. I want to use these sets to filter a sequence of Products in a semi-inclusive fashion. Here's the code:
List<IEnumerable<ITextEntity>> InclusiveFilters;
IQueryable<Product> Products;
//...snip...
Products = Products.Where(prod =>
InclusiveFilters.All(filter =>
filter.Any(iTextEnt =>
iTextEnt.Product.Contains(prod)
)
)
);
So what I'm trying to do is this:
I have a set, Products, that I want to filter.
For each of several types that implement ITextEntity, there's a
set S of entities of that type.
Each object O has a set O.P of products.
For each Product prod in Products,
for each set S,
for at least one O in S,
O.P must contain prod. If not, remove prod from Products.
As you can see, this is pretty complicated.
I have the feeling that this is caused by LINQ not being able to work with the ITextEntity type, rather than a problem with my set operations. However, the complexity of the above is making this difficult to work with, and difficult to find a non-LINQ alternative. It's going to get pretty ugly if I can't use LINQ.
I found an MSDN page and a Stack Overflow thread discussing similar exceptions, but neither was much help. Another SO thread aims a finger at my use of the Contains method, but because of the complexity here, I haven't had much luck trying to replace it with the BuildOrExpression method. I doubt BuildOrExpression will work anyway, since this is EF 4 and Contains is supposed to be supported.
So I'm rather stuck here. Can anyone advise?
EDIT: This question was answered in Aug 2010, but I've come back to clean up the title and description.
You're implicitly casting to ITextEntity. But ITextEntity is not part of your entity data model, so the EF doesn't know how to translate its members into SQL. Contains is supported, but only with primitive or entity types. Also, you use IEnumerable, which also prevents translation to SQL; you need IQueryable to be convertable to SQL.
So if you remove the interface reference and IEnumerable, you should be able to execute the query on the DB server. Otherwise you have to go into L2O (with, e.g., AsEnumerable()).