With following DDD and the repository pattern, is it possible to return the aggregate root object with its child data already included instead of using lazy loading?
e.g. I have a warehouse entity as the aggregate root and it has a child object called location.
On the repository I have a method below to query the location Id but passes back the warehouse entity.
dim warehouse as Warehouse = warehouseRepository.FindByLocationId(Id as int32).
dim locationName as string = warehouse.location.where(function(x) x.Id = 1).firstordefault.name
When I use warehouse.location EF uses a proxy class to fire off another DB query to retrieve the location data.
In my repository method FindByLocationId can I query the location DB table and pass back the warehouse entity with the location data included?
In general to stop lazy loading and proxies you can set the following properties on the Configuration property of your DbContext class. I tend to do this when overriding the OnModelCreating() method, so all my 'Setup' stuff is together.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
base.OnModelCreating(modelBuilder);
}
If you want to eagerly load a property you can use the Include() method:
var wareHouse = (from w in ctx.WareHouses.Include("location")
select w).FirstOrDefault();
I presume you just want to use the include option in your query. http://msdn.microsoft.com/en-us/library/bb896272.aspx
So you'd have something like this:
var data = (from w in context.Warehouse
.Include("Location")
select w).FirstOrDefault();
Related
I have a EF core code first web api application.
There is a Products entity and a UserProductsRating child entity (with a one to many relationship)
I also wanted to have an average review score (and be able to select/sort based on it) so created a view to do this (using the method described in this answer
[https://stackoverflow.com/a/18707413][1])
So the migration for my View looks like:
protected override void Up(MigrationBuilder migrationBuilder)
{
string script =
#"
CREATE VIEW AverageProductRating AS
SELECT u.ProductId, AVG(CAST(u.Rating AS FLOAT)) as AverageRating
FROM dbo.UserRatings u GROUP BY u.ProductId";
migrationBuilder.Sql(script);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
string script = #"DROP VIEW dbo.AverageProductRating";
migrationBuilder.Sql(script);
}
Then there is an AverageRating entity on top.
This all works fine and allows me to create queries like:
var top5Products = _db.Products.Include(x => x.AverageProductRating)
.Where(x => x.AverageProductRating != null)
.OrderByDescending(x => x.AverageProductRating.AverageRating)
.Take(5);
The problem occurs when I get to my Unit/Integration Tests. I am using the InMemoryDatabase with EnsureCreated to set up a testing instance/seed data.
var options = new DbContextOptionsBuilder<ProductsContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.EnableSensitiveDataLogging()
.Options;
var context = new ProductsContext(options);
context.Database.EnsureCreated();
When I run tests against this the AverageProductRating Entity always has zero rows (I'm not sure if the View gets created at all or not)
I think this may be to do with restrictions in SQL in the inmemory db or the way migrations are run, but I'm not sure.
Any suggestions on how to work round this would be welcome
Thanks
I have a problem with refreshing the data from the database. See the code below. What I try to accomplish here is this: I create a ProductProtocol (p) and add one ProductProtocolContent to the list of ProductProtocolContents. Then I save the whole lot to the database. After that I simulate a change I make (the addition of another ProductProtocolContent ) that I want to undo and I want to test that after refreshing the object using my repository the
var p = new ProductProtocol { Name = "test1" };
p.ProtocolContents.Add(new ProductProtocolContent { ValidFrom = DateTime.Now, Value = "9_qwe2341" });
var r = RepositoryFactory.Create<ProductProtocol>(unitOfWork);
r.Add(p);
unitOfWork.Commit();
// add another ProductProtocolContent, p.ProductProtocolContents.Count == 2
p.ProtocolContents.Add(new ProductProtocolContent { ValidFrom = DateTime.Now, Value = "9_truf7461" });
// undo the changes to the object
r.Refresh(p);
// reload the ProductProtocolContents from the database
r.LoadPropertySet(p, pc => pc.ProtocolContents);
// p.ProductProtocolContents.Count should be 1
Assert.IsTrue(p.ProtocolContents.Count == 1);
My problem is that p.ProtocolContents.Count == 0 at the time Assert.IsTrue is called.
The implementation of r.LoadPropertySet() is as follows:
public void LoadPropertySet<Y>(T target, Expression<Func<T, ICollection<Y>>> spec) where Y : BaseBusinessObject
{
context.Entry(target).Collection(spec).CurrentValue.Clear();
context.Entry(target).Collection(spec).Load();
}
using Entity Framework 4 (using the ObjectContext instead of DbContext)I simply did this and that worked:
context.LoadProperty(target, spec, MergeOption.OverwriteChanges);
How would I do this using the DbSet, Must I convert back to ObjectContext or is there a equivalent for DbSet?
If you can't do it on DBContext, you can always cast to ObjectContext and use its functionality.
MSDN: DbContext wraps ObjectContext and exposes the most commonly used features of ObjectContext by using simplified and more intuitive APIs. You can access the underlying ObjectContext whenever you need to use features that are not supported by DbContext.
I'm using entity framework with POCOs and the repository pattern and am wondering if there is any way to filter a child list lazy load. Example:
class Person
{
public virtual Organisation organisation {set; get;}
}
class Organisation
{
public virtual ICollection<Product> products {set; get;}
}
class Product
{
public bool active {set; get;}
}
Currently I only have a person repository because I'm always starting from that point, so ideally I would like to do the following:
Person person = personRepo.GetById(Id);
var products = person.organisation.products;
And have it only load products where active = true from the database.
Is this possible and if so how?
EDIT My best guess would be either a filter can be added to the configuration of the entity. Or there might be a way to intercept/override the lazy load call and modify it. Obviously if I created an Organisation Repository I could manually load it as I please but I am trying to avoid that.
There's not a direct way to do this via lazy loading, but if you were willing to explicitly load the collection, you could follow whats in this blog, see the Applying filters when explicitly loading related entities section.
context.Entry(person)
.Collection(p => p.organisation.products)
.Query()
.Where(u => u.IsActive)
.Load();
You can do what Mark Oreta and luksan suggest while keeping all the query logic within the repository.
All you have to do is pass a Lazy<ICollection<Product>> into the organization constructor, and use the logic they provided. It will not evaluate until you access the value property of the lazy instance.
UPDATE
/*
First, here are your changes to the Organisation class:
Add a constructor dependency on the delegate to load the products to your
organization class. You will create this object in the repository method
and assign it to the Person.Organization property
*/
public class Organisation
{
private readonly Lazy<ICollection<Product>> lazyProducts;
public Organisation(Func<ICollection<Product>> loadProducts){
this.lazyProducts = new Lazy<ICollection<Product>>(loadProducts);
}
// The underlying lazy field will not invoke the load delegate until this property is accessed
public virtual ICollection<Product> Products { get { return this.lazyProducts.Value; } }
}
Now, in your repository method, when you construct the Person object you will assign the Organisation property with an Organisation object containing the lazy loading field.
So, without seeing your whole model, it will looks something like
public Person GetById(int id){
var person = context.People.Single(p => p.Id == id);
/* Now, I'm not sure about the cardinality of the person-organization or organisation
product relationships, but let's assume you have some way to access the PK of the
organization record from the Person and that the Product has a reference to
its Organisation. I may be misinterpreting your model, but hopefully you
will get the idea
*/
var organisationId = /* insert the aforementioned magic here */
Func<ICollection<Product>> loadProducts = () => context.Products.Where(product => product.IsActive && product.OrganisationId == organisationId).ToList();
person.Organisation = new Organisation( loadProducts );
return person;
}
By using this approach, the query for the products will not be loaded until you access the Products property on the Organisationinstance, and you can keep all your logic in the repository. There's a good chance that I made incorrect assumptions about your model (as the sample code is quite incomplete), but I think there is enough here for you to see how to use the pattern. Let me know if any of this is unclear.
This might be related:
Using CreateSourceQuery in CTP4 Code First
If you were to redefine your properties as ICollection<T> rather than IList<T> and enable change-tracking proxies, then you might be able to cast them to EntityCollection<T> and then call CreateSourceQuery() which would allow you to execute LINQ to Entities queries against them.
Example:
var productsCollection = (EntityCollection<Product>)person.organisation.products;
var productsQuery = productsCollection.CreateSourceQuery();
var activeProducts = products.Where(p => p.Active);
Is your repository using something like:
IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> expression)
If so you can do something like this:
var person = personRepo.Find(p => p.organisation.products.Any(e => e.active)).FirstOrDefault();
You could possibly use Query() method to achieve this. Something like:
context.Entry(person)
.Collection(p => p.organisation.products)
.Query()
.Where(pro=> pro.Active==true)
.Load();
Have a look at this page click here
I'm currently working on a project which is using EF Code First with POCOs. I have 5 POCOs that so far depends on the POCO "User".
The POCO "User" should refer to my already existing MemberShip table "aspnet_Users" (which I map it to in the OnModelCreating method of the DbContext).
The problem is that I want to take advantage of the "Recreate Database If Model changes" feature as Scott Gu shows at: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - What the feature basically does is to recreate the database as soon as it sees any changes in my POCOs. What I want it to do is to Recreate the database but to somehow NOT delete the whole Database so that aspnet_Users is still alive. However it seems impossible as it either makes a whole new Database or replaces the current one with..
So my question is: Am I doomed to define my database tables by hand, or can I somehow merge my POCOs into my current database and still take use of the feature without wipeing it all?
As of EF Code First in CTP5, this is not possible. Code First will drop and create your database or it does not touch it at all. I think in your case, you should manually create your full database and then try to come up with an object model that matches the DB.
That said, EF team is actively working on the feature that you are looking for: altering the database instead of recreating it:
Code First Database Evolution (aka Migrations)
I was just able to do this in EF 4.1 with the following considerations:
CodeFirst
DropCreateDatabaseAlways
keeping the same connection string and database name
The database is still deleted and recreated - it has to be to for the schema to reflect your model changes -- but your data remains intact.
Here's how: you read your database into your in-memory POCO objects, and then after the POCO objects have successfully made it into memory, you then let EF drop and recreate the database. Here is an example
public class NorthwindDbContextInitializer : DropCreateDatabaseAlways<NorthindDbContext> {
/// <summary>
/// Connection from which to ead the data from, to insert into the new database.
/// Not the same connection instance as the DbContext, but may have the same connection string.
/// </summary>
DbConnection connection;
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map;
public NorthwindDbContextInitializer(DbConnection connection, Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> map = null) {
this.connection = connection;
this.map = map ?? ReadDataIntoMemory();
}
//read data into memory BEFORE database is dropped
Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> ReadDataIntoMemory() {
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map = new Dictionary<Tuple<PropertyInfo,Type>,System.Collections.IEnumerable>();
switch (connection.State) {
case System.Data.ConnectionState.Closed:
connection.Open();
break;
}
using (this.connection) {
var metaquery = from p in typeof(NorthindDbContext).GetProperties().Where(p => p.PropertyType.IsGenericType)
let elementType = p.PropertyType.GetGenericArguments()[0]
let dbsetType = typeof(DbSet<>).MakeGenericType(elementType)
where dbsetType.IsAssignableFrom(p.PropertyType)
select new Tuple<PropertyInfo, Type>(p, elementType);
foreach (var tuple in metaquery) {
map.Add(tuple, ExecuteReader(tuple));
}
this.connection.Close();
Database.Delete(this.connection);//call explicitly or else if you let the framework do this implicitly, it will complain the connection is in use.
}
return map;
}
protected override void Seed(NorthindDbContext context) {
foreach (var keyvalue in this.map) {
foreach (var obj in (System.Collections.IEnumerable)keyvalue.Value) {
PropertyInfo p = keyvalue.Key.Item1;
dynamic dbset = p.GetValue(context, null);
dbset.Add(((dynamic)obj));
}
}
context.SaveChanges();
base.Seed(context);
}
System.Collections.IEnumerable ExecuteReader(Tuple<PropertyInfo, Type> tuple) {
DbCommand cmd = this.connection.CreateCommand();
cmd.CommandText = string.Format("select * from [dbo].[{0}]", tuple.Item2.Name);
DbDataReader reader = cmd.ExecuteReader();
using (reader) {
ConstructorInfo ctor = typeof(Test.ObjectReader<>).MakeGenericType(tuple.Item2)
.GetConstructors()[0];
ParameterExpression p = Expression.Parameter(typeof(DbDataReader));
LambdaExpression newlambda = Expression.Lambda(Expression.New(ctor, p), p);
System.Collections.IEnumerable objreader = (System.Collections.IEnumerable)newlambda.Compile().DynamicInvoke(reader);
MethodCallExpression toArray = Expression.Call(typeof(Enumerable),
"ToArray",
new Type[] { tuple.Item2 },
Expression.Constant(objreader));
LambdaExpression lambda = Expression.Lambda(toArray, Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(tuple.Item2)));
var array = (System.Collections.IEnumerable)lambda.Compile().DynamicInvoke(new object[] { objreader });
return array;
}
}
}
This example relies on a ObjectReader class which you can find here if you need it.
I wouldn't bother with the blog articles, read the documentation.
Finally, I would still suggest you always back up your database before running the initialization. (e.g. if the Seed method throws an exception, all your data is in memory, so you risk your data being lost once the program terminates.) A model change isn't exactly an afterthought action anyway, so be sure to back your data up.
One thing you might consider is to use a 'disconnected' foreign key. You can leave the ASPNETDB alone and just reference the user in your DB using the User key (guid). You can access the logged in user as follows:
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
And then use the User's key as a FK in your DB:
Guid UserId = (Guid) currentUser.ProviderUserKey ;
This approach decouples your DB with the ASPNETDB and associated provider architecturally. However, operationally, the data will of course be loosely connected since the IDs will be in each DB. Note also there will be no referential constraints, whcih may or may not be an issue for you.
I have an object that has been populated with the contents of four different related entities. However i have another entity in which i cannot include as part of the query due to it not being related in the navigation properites directly to the IQueryable table i am pulling. The entity i am trying to include is related to one of the four different entities that have been included successfully.
Is there a way to include(during db hit or afterwards) this entity as part of the overall object i am creating?
Here is an example of what my calls look like to build the CARTITEM object:
public List<CARTITEM> ListCartItem(Guid cartId)
{
//Create the Entity object
List<CARTITEM> itemInfo = null;
using (Entities webStoreContext = new Entities())
{
//Invoke the query
itemInfo = WebStoreDelegates.selectCartItems.Invoke(webStoreContext).ByCartID(cartId).ToList();
}
//Return the result set
return itemInfo;
}
here is the selectCartItems filter(Where i would normally do the includes):
public static Func<Entities, IQueryable<CARTITEM>> selectCartItems =
CompiledQuery.Compile<Entities, IQueryable<CARTITEM>>(
(cart) => from c in cart.CARTITEM.Include("ITEM").Include("SHIPPINGOPTION").Include("RELATEDITEM").Include("PROMOTION")
select c);
from this i have my CARTITEM object. Problem is i want to include the PROMOTIONTYPE table in this object, but since the CARTIEM entity doesn't have a navigation property directly to the PROMOTIONTYPE table i get an error.
Let me know if you need any more clarification.
Thanks,
Billy
You can use join and if it is the same database and server it should generate the join in SQL and do it all in one call...
LinqToEnties join example