I'm using EF4.3 with DbContext.
I have an entity that I store in cache, so I need to eager load the necessary data before converting to a list and popping it in cache.
My database is normalised so data is spread over several tables. The base entity is "User", a User may or may not be a "Subscriber" and a Subscriber can be one of 3 types "Contributor", "Member" or "Administrator"
At present the whole fetch is not very elegant due to my lack of knowledge in EF, Linq et al.
public static User Get(Guid userId)
{
Guard.ThrowIfDefault(userId, "userId");
var r = new CrudRepo<User>(Local.Items.Uow.Context);
var u = r.FindBy(x => x.UserId == userId)
.Include("BookmarkedDeals")
.Include("BookmarkedStores")
.SingleOrDefault();
if (u.IsNotNull() && u.IsActive)
{
if (u.IsAdmin)
{
u.GetAdministrator();
}
else if (u.IsContributor)
{
u.GetContributor();
}
else if (u.IsMember)
{
u.GetMember();
}
else
{
string.Format("Case {0} not implemented", u.UserRoleId)
.Throw<NotImplementedException>();
}
}
return u;
}
Each of the 'Get' methods gets a Subscriber entity plus the relevant Include() entities for the role type.
I'm pretty sure it can be done a whole lot more elegently than this but struggling with the initial thought process.
Anyone help?
UPDATED with example of one of the Get methods
public static void GetMember(this User user)
{
Guard.ThrowIfNull(user, "user");
var r = new ReadRepo<Subscriber>(Local.Items.Uow.Context);
user.Subscriber = r.FindBy(x => x.UserId == user.UserId)
.Include("Kudos")
.Include("Member.DrawEntries")
.Include("Member.FavouriteCategories")
.Include("Member.FavouriteStores")
.Single();
}
If your "User" entity is connected to your other entities you can use the Load method of the connected entity collection to get the related entities. For example if your "User" entity has a property "Subscriber" you could call u.Subscriber.Load() to get the related entity. Here is the related MSDN article
var u = r.FindBy(x => x.UserId == userId)
.Include("BookmarkedDeals")
.Include("BookmarkedStores")
.SingleOrDefault();
if(someCondition)
{
u = u.Include("something");
}
Don't have a place to test this, but have you tried that?
Related
I don't have a problem currently, but I want to make sure, that the performance is not too shabby for my issue. My search on Microsofts documentation was without any success.
I have a Entity of the name Reservation. I now want to add some statistics to the program, where I can see some metrics about the reservations (reservations per month and favorite spot/seat in particular).
Therefore, my first approach was the following:
public async Task<ICollection<StatisticElement<Seat>>> GetSeatUsage(Company company)
{
var allReservations = await this.reservationService.GetAll(company);
return await this.FetchGroupedSeatData(allReservations, company);
}
public async Task<ICollection<StatisticElement<DateTime>>> GetMonthlyReservations(Company company)
{
var allReservations = await this.reservationService.GetAll(company);
return this.FetchGroupedReservationData(allReservations);
}
private async Task<ICollection<StatisticElement<Seat>>> FetchGroupedSeatData(
IEnumerable<Reservation> reservations,
Company company)
{
var groupedReservations = reservations.GroupBy(r => r.SeatId).ToList();
var companySeats = await this.seatService.GetAll(company);
return (from companySeat in companySeats
let groupedReservation = groupedReservations.FirstOrDefault(s => s.Key == companySeat.Id)
select new StatisticElement<Seat>()
{
Value = companySeat,
StatisticalCount = groupedReservation?.Count() ?? 0,
}).OrderByDescending(s => s.StatisticalCount).ToList();
}
private ICollection<StatisticElement<DateTime>> FetchGroupedReservationData(IEnumerable<Reservation> reservations)
{
var groupedReservations = reservations.GroupBy(r => new { Month = r.Date.Month, Year = r.Date.Year }).ToList();
return groupedReservations.Select(
groupedReservation => new StatisticElement<DateTime>()
{
Value = new DateTime(groupedReservation.Key.Year, groupedReservation.Key.Month, 1),
StatisticalCount = groupedReservation.Count(),
}).
OrderBy(s => s.Value).
ToList();
}
To explain the code a little bit: With GetSeatUsage and GetMonthlyReservations I can get the above mentioned data of a company. Therefore, I fetch ALL reservations at first (with reservationService.GetAll) - this is the point, where I think the performance will be a problem in the future.
Afterwards, I call either FetchGroupedSeatData or FetchGroupedReservationData, which first groups the reservations I previously fetched from the database and then converts them in a, for me, usable format.
As I said, I think the group by after I have read ALL the data from the database MIGHT be a problem, but I cannot find any information regarding performance in the documentation.
My other idea was, that I create a new method in my ReservationService, which then already returns the grouped list. But, again, I can't find the information, that the EF adds the GroupBy to the DB Query or basically does it after all of the data has been read from the database. This method would look something like this:
return await this.Context.Set<Reservation>.Where(r => r.User.CompanyId == company.Id).GroupBy(r => r.SeatId).ToListAsync();
Is this already the solution? Where can I check that? Am I missing something completely obvious?
While learning Entity Framework 6, I have hit an obstacle and unsure how to handle a situation. While making an API, a user may want a specific endpoint which requires access to multiple tables (Fake Entity, since it does not have a real table association). Below is some fake DbSets and a random class.
I am looking for a way to include all of these tables data (With Where Clauses) all in 1 query. I was doing 3 separate calls, but I don't think this is the best way.
var anonObject = new AnonClass()
{
SometItems = await Context.Table1.Where(t => t.Something == true).ToListAsync();
SometItems2 = await Context.Table2.Where(t => t.Something == true).ToListAsync();
SometItems3 = await Context.Table3.Where(t => t.Something == true).ToListAsync();
};
DbSet<Table1> Table1;
DbSet<Table2> Table2;
DbSet<Table3> Table3;
public sealed AnonClass
{
public IEnumerable<Table1> SomeItems;
public IEnumerable<Table2> SomeItems2;
public IEnumerable<Table3> SomeItems3;
}
Each of these are individual calls, I want them all in one.
The current setup of EF in my application is lazy loading, which is great for the most part. However, I am lost trying work out how to load a list of related entities based on their IsEnabled bit attribute.
In this example I am just returning a list of entities.
return Context.Entities.ToList()
Let's say the Entities object contains a list of ChildEntities like so:
public class Entities
{
private string EntityName;
private List<ChildEntities> ChildEntities;
}
public class ChildEntites
{
private string ChildEntityName;
private bool IsEnabled;
}
I want to only want to get out the ChildEntities based on their IsEnabled flag when loading the list of Entities.
You can use the Include() method to load all child entities and then select only those which are enabled like
Context.Entities.Include("ChildEntites").Select(c => e.IsEnabled == true)
Another way would be to get the filter entities and then run the query as shown in this post
var data = from e in Context.Entities
select new
{
Entities = e,
Childs = e.ChildEntites.Where(c => c.IsEnabled == true)
};
var Results = data.ToArray().Select(x => x.Entities);
I think there is no way to filter when you load related entities in case that you use lazy loading or eager loading unless you project your query to an anonymous type or a DTO, but if you have an entity instance,you can load related entities based on a condition using explicit loading:
var entity=context.Entities.FirstOrDefault();
context.Entry(entity)
.Collection(b => b.ChildEntities)
.Query()
.Where(ce => ce.IsEnabled == true)
.Load();
If that doesn't satisfy what you are trying to achieve because you need to load the entire entity collection, then, as I said before, you should project your query to a custom class or an anonymous type:
var query= from e in Context.Entities.Include(c=>c.ChildEntities)
select new EntityDTO
{
EntityName= e.EntityName,
ChildEntites= e.ChildEntites.Where(c => c.IsEnabled == true)
};
Using a projection
var entities = context.Entities
.Select(x => new {x, x.ChildEntities.Where(y => y.IsEnabled))
.ToList() // resolve from database before selecting the main entity
.Select(x => x.x);
Using a third party library
EF+ Query IncludeFilter allow you to easily filter related entities
var entities = context.Entities.IncludeFilter(x => x.ChildEntities.Where(y => y.IsEnabled))
.ToList();
You can find the documentation here
Disclaimer: I'm the owner of the project EF+.
A couple ways I would recommend this approach. I would either lazy load where IsEnabled = false and eager load in a separate call where IsEnabled = true OR once you have the lazy loaded collection, in a separate call, get the children where IsEnabled = true. I don't believe you will be able to do this in one call. The other option would be a stored procedure. I hope this helps.
I had a similar problem. I solved it in the following manner.
Create a new method in your model Entities. Lets call it ChildEntitiesEnabled
public ICollection<ChildEntity> ChildEntitiesEnabled()
{
//First I get the full list using the lazy loading...
var allChildEntities=ChildEntities.ToList();
//do further processing if there is data
if(allChildEntities!=null && allChildEntities.Count()>0)
{
var childEntitiesEnabled = ChildEntities.Where(x=>x.Enabled==true).ToList();
return childEntitiesEnabled;
}
return null; //or you can return an empty list...
}
I like this method because you can use it anywhere the model is available without complicated code strewn all over the place. Also, you dont lose all the ChildEntities data... that is available too from the original call.
Say my object graph looks like:
User
=> Friends
=> Clicks
=> Urls
So when I load a User, I also want to eagirly load the navigation property Friends. And I want the friend object to eagirly load Clicks and so on.
Using the code I have now I can only do this for 1 level:
public User GetUserById(int userId)
{
return Get(x => x.Id == userId, includeProperties: "Friends").FirstOrDeafult();
}
Is this possible?
I'm using MVC 4.1
I'm currently using the repository pattern based on http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Apparently this repository implementation is just splitting the includeProperties parameter by comma (includeProperties.Split(new char[] { ',' }) and then calls
query = query.Include(includeProperty);
for each element in the result array of the split. For your example you can use a dotted path then:
return Get(x => x.Id == userId, includeProperties: "Friends.Clicks.Urls")
.FirstOrDefault();
It will load all entities on the path from the root User entity to the last navigation property Urls.
If you had another navigation property in User - say Profile - and you would want to eagerly load that as well, it seems that this syntax is supported then:
return Get(x => x.Id == userId, includeProperties: "Profile,Friends.Clicks.Urls")
.FirstOrDefault();
Let's assume I have a file with multiple rows each representing a Person, the Person entity has an identity column that is also the primary key. Assuming that a Person can be repeated in the file, if it is, perhaps I want to do a last entry wins scenario. In the example below I use a repository to retrieve a person from the database by the social security number. The issue is that when the same SSN shows up again in the file, the repository still returns a null, even though technically that person with that SSN has already been added to the context (SaveChanges hasn't been called yet). I realize I can work around this by tracking myself which Person objects have already been added. I am wondering what is the best practice for this scenario.
Thanks.
foreach(row in fileRows)
{
Person person = personRepository.GetBySSN(row.SSN);
if(person == null)
{
//insert logic
}
else
{
//update logic
}
}
personRepository.SaveChanges();
I think I can give the same answer I gave here
To persist an entity you usually add it to it's DbSet in the context.
For example
var bar = new Bar();
bar.Name = "foo";
var context = new Context();
context.Bars.Add(bar);
Surprisingly, querying context.Bars, the just added entity cannot be found
var howMany = context.Bars.Count(b => b.Name == "foo");
// howMany == 0
After context.SaveChanges() the same line will result 1
The DbSet seems unaware to changes until they're persisted on db.
Fortunately, each DbSet has a Local property that acts like the DbSet itself, but it reflect all in-memory operations
var howMany = context.Bars.Local.Count(b => b.Name == "foo");
// howMany == 1
You can also use Local to add entities
context.Bars.Local.Add(bar);
and get rid of the weird behavior of Entity Framework.
Modify your GetBySSN as follows:
public Person GetBySSN(string ssn)
{
Person p = context.ObjectStateManager
.GetObjectStateEntries(~EntityState.Deleted)
.Where(e => !e.IsRelationship)
.Select(e => e.Entity)
.OfType<Person>()
.SingleOrDefault(p => p.SSN = ssn);
if (p == null)
{
p = context.People.SingleOrDefault(p => p.SSN = ssn);
}
return p;
}
If you want to go Ladislav's route and query the ObjectStateManager then you may wish to use an extension method like the following:
public static IEnumerable<TEntity> LoadedEntities<TEntity>(this ObjectContext Context)
{
return Context.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Unchanged)
.Where(e => !e.IsRelationship).Select(e => e.Entity).OfType<TEntity>();
}
This would allow you to then do something like this:
Person p = context.LoadedEntities<Person>().SingleOrDefault(p => p.SSN = ssn);
if (p != null)
return p;
return context.People.SingleOrDefault(p => p.SSN = ssn);
I haven't found anything regarding best practices so I'll post my solution. I know many other posts refer to the fact that the EF context provides mechanisms for looking into it and seeing if a particular entity is in attached state. Being that I work through repositories (my business layer has no direct access to the EF context), my choice is either offloading this sort of logic into the repository, or attempt to solve it in the business layer. My feeling is that this task is really a business concern and not a data access layer concern.
Dictionary<string, Person> newPeople = ...
foreach(row in fileRows)
{
Person person;
//if the person is already tracked by the dictionary, work with it, if not use the repository
if(!newPeople.TryGetValue(row.SSN, out person))
{
person = personRepository.GetBySSN(row.SSN);
}
if(person == null)
{
//insert logic
//keep track that this person is already in line for inserting
newPeople.Add(row.SSN, person);
}
else
{
//update logic
}
}
personRepository.SaveChanges();