Entity Framework Core OwnsOne unnecessary JOINS on same table - sql

Entity framework core seems to create very complex queries when configuring value objects with OwnsOne on the same table.
Entity: Restaurant.cs
public class Restaurant : Entity, IAggregateRoot
{
public RestaurantId Id { get; private set; }
public string Name { get; private set; }
public Address Address { get; private set; }
protected Restaurant()
{
}
public Restaurant SetAddress(double lat, double lng, string street, string location, string zip, string country)
{
Address = new Address(lat, lng, street, location, zip, country);
return this;
}
}
Value object: Address.cs
public class Address : ValueObject
{
public Point Location { get; set; }
public string Street { get; private set; }
public string City { get; private set; }
public string Country { get; private set; }
public string Zip { get; private set; }
private Address() { }
public Address(double lat, double lng, string street, string city, string zip, string country)
{
Location = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326).CreatePoint(new Coordinate(lng, lat));
Street = street;
City = city;
Country = country;
Zip = zip;
}
protected override IEnumerable<object> GetAtomicValues()
{
yield return Street;
yield return City;
yield return Country;
yield return Zip;
}
}
EF Config: RestaurantEntityTypeConfig.cs
internal class RestaurantEntityTypeConfig : IEntityTypeConfiguration<Restaurant>
{
public void Configure(EntityTypeBuilder<Restaurant> builder)
{
builder.ToTable("Restaurant", SchemaNames.Restaurant);
builder.HasKey(c => c.Id);
builder.OwnsOne(c => c.Address, c=>
{
c.WithOwner();
});
}
}
Repository method:
public virtual async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate, params public virtual async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
var query = _dbContext.Set<T>().Where(predicate);
query = includeProperties.Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
return await query.FirstOrDefaultAsync();
}
The query when selecting through repository looks like this:
SELECT TOP(1) [r].[Id], [r].[Name], [t].[Id], [t].[Address_City], [t].[Address_Country], [t].[Address_State], [t].[Address_Street], [t].[Address_ZipCode]
FROM [restaurant].[Restaurant] AS [r]
LEFT JOIN (
SELECT [r0].[Id], [r0].[Address_City], [r0].[Address_Country], [r0].[Address_State], [r0].[Address_Street], [r0].[Address_ZipCode], [r1].[Id] AS [Id0]
FROM [restaurant].[Restaurant] AS [r0]
INNER JOIN [restaurant].[Restaurant] AS [r1] ON [r0].[Id] = [r1].[Id]
WHERE [r0].[Address_ZipCode] IS NOT NULL OR ([r0].[Address_Street] IS NOT NULL OR ([r0].[Address_State] IS NOT NULL OR ([r0].[Address_Country] IS NOT NULL OR [r0].[Address_City] IS NOT NULL)))
) AS [t] ON [r].[Id] = [t].[Id]
WHERE [r].[Id] = #__p_0
The sql table looks like this:
My expected result would be a flat query since the value object is persisted to the same sql table. Is this a bug of entity framework or am I missing a configuration?

Related

Take function doesn't work and cannot be sent to RavenDB for query

Query Code:
var query = session.IndexQuery<App_OrgSearch.IndexResult, App_OrgSearch>();
var organizationUnitResults = query.Statistics(out stats)
.Skip(0)
.Take(5)
.AsProjection<Org>().ToList();
public static IRavenQueryable<TResult> IndexQuery<TResult, TIndex>(this IDocumentSession session)
where TIndex : AbstractIndexCreationTask, new()
{
return session.Query<TResult, TIndex>();
}
App_OrgSearch is the index I defined as below:
public class App_OrgSearch : AbstractIndexCreationTask<Org, App_OrgSearch.IndexResult>
{
public class IndexResult
{
public string Id { get; set; }
public string BusinessName { get; set; }
public string ShortName { get; set; }
public IList<string> Names { get; set; }
public List<string> PhoneNumbers { get; set; }
public List<OrganizationUnitPhone> OrganizationUnitPhones { get; set; }
}
public App_OrganizationUnitSearch()
{
Map = docs => from doc in docs
select new
{
Id = doc.Id,
Names = new List<string>
{
doc.BusinessName,
doc.ShortName,
},
BusinessName = doc.BusinessName,
ShortName = doc.ShortName,
PhoneNumbers = doc.OrganizationUnitPhones.Where(x => x != null && x.Phone != null).Select(x => x.Phone.Number),
};
Indexes.Add(x => x.Names, FieldIndexing.Analyzed);
}
}
I have 27 records in database. I want to take 5, but after query, all 27 records are returned. Why does Take function not work?
Your sample code seems wrong.
var query = session.IndexQuery<App_OrgSearch.IndexResult, App_OrgSearch>();
var organizationUnitResults = organizationUnitsQuery.Statistics(out stats)
What is organizationUnitsQuery ? You have the query as query, but there is no IndexQuery method on the session

Can I use an index as the source of an index in RavenDB

I'm trying to define an index in RavenDb that uses the output of another index as it's input but I can't get it to work.
I have the following entities & indexes defined.
SquadIndex produces the result I expect it to do but SquadSizeIndex doesn't even seem to execute.
Have I done something wrong or is this not supported?
class Country
{
public string Id { get; private set; }
public string Name { get; set; }
}
class Player
{
public string Id { get; private set; }
public string Name { get; set; }
public string CountryId { get; set; }
}
class Reference
{
public string Id { get; set; }
public string Name { get; set; }
}
class SquadIndex : AbstractIndexCreationTask<Player, SquadIndex.Result>
{
public SquadIndex()
{
Map = players => from player in players
let country = LoadDocument<Country>(player.CountryId)
select new Result
{
Country = new Reference
{
Id = country.Id,
Name = country.Name
},
Players = new[]
{
new Reference
{
Id = player.Id,
Name = player.Name
}
}
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
Players = g.SelectMany(x => x.Players)
};
}
internal class Result
{
public Reference Country { get; set; }
public IEnumerable<Reference> Players { get; set; }
}
}
class SquadSizeIndex : AbstractIndexCreationTask<SquadIndex.Result, SquadSizeIndex.Result>
{
public SquadSizeIndex()
{
Map = squads => from squad in squads
select new Result
{
Country = squad.Country,
PlayerCount = squad.Players.Count()
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
PlayerCount = g.Sum(x => x.PlayerCount)
};
}
internal class Result
{
public Reference Country { get; set; }
public int PlayerCount { get; set; }
}
}
No, you can't. The output of indexes are not documents to be indexed.
You can use the scripted index results to chain indexes, but that isn't trivial.

RavenDB TransformResults

I'm trying to use the TransformResults feature, and I can't get it to work. I'm not totally sure I understand this feature, perhaps there is another way to solve this problem. What I want is just the Id from the Order and the email addesses from the Customer and the Entrepreneur. I am happy for all tips that can take me in the right direction. Here is my code.
Document
public class OrderDocument
public string Id {get; set }
public EntrepreneurInfo EntrepreneurInfo { get; set; }
public CustomerInfo CustomerInfo { get; set; }
public OrderStatus CurrentOrderStatus { get; set; }
}
Info classes
public class EntrepreneurInfo
{
public string EntrepreneurDocumentId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
public class CustomerInfo
{
public string CustomerDocumentId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
The info classes are just subsets of a Customer and Entrepreneur documents respectively.
The Customer and Entrepreneur documents inherits from a base class ( AbstractOrganizationDocument) that has the EmailAddress property.
My Index
public class OrdersApprovedBroadcastingData :
AbstractIndexCreationTask<OrderDocument, OrdersApprovedBroadcastingData.ReduceResult>
{
public OrdersApprovedBroadcastingData()
{
this.Map = docs => from d in docs
where d.CurrentOrderStatus == OrderStatus.Approved
select new
{
Id = d.Id,
CustomerId = d.CustomerInfo.CustomerDocumentId,
EntrepreneurId = d.EntrepreneurInfo.EntrepreneurDocumentId
};
this.TransformResults = (db, orders) => from o in orders
let customer = db.Load<CustomerDocument>(o.CustomerId)
let entrepreneur = db.Load<EntrepreneurDocument>(o.EntrepreneurId)
select
new
{
o.Id,
o.CustomerId,
CustomerEmail = customer.EmailAddress,
o.EntrepreneurId,
EntrepreneurEmail = entrepreneur.EmailAddress
};
}
public class ReduceResult
{
public string Id { get; set; }
public string CustomerId { get; set; }
public string CustomerEmail { get; set; }
public string EntrepreneurId { get; set; }
public string EntrepreneurEmail { get; set; }
}
}
If I look at the result of this Index in Raven Studio I get null values for all fields except the Id. And finally here is my query.
Query
var items =
this.documentSession.Query<OrdersApprovedBroadcastingData.ReduceResult, OrdersApprovedBroadcastingData>()
.Select(x => new OrdersToBroadcastListItem
{
Id = x.Id,
CustomerEmailAddress = x.CustomerEmail,
EntrepreneurEmailAddress = x.EntrepreneurEmail
}).ToList();
Change your index to:
public class OrdersApprovedBroadcastingData : AbstractIndexCreationTask<OrderDocument>
{
public OrdersApprovedBroadcastingData()
{
Map = docs => from d in docs
where d.CurrentOrderStatus == OrderStatus.Approved
select new
{
};
TransformResults = (db, orders) =>
from o in orders
let customer = db.Load<CustomerDocument>(o.CustomerInfo.CustomerDocumentId)
let entrepreneur = db.Load<EntrepreneurDocument>(o.EntrepreneurInfo.EntrepreneurDocumentId)
select new
{
o.Id,
CustomerEmailAddress = customer.EmailAddress,
EntrepreneurEmailAddress = entrepreneur.EmailAddress
};
}
}
Your result class can simply be the final form of the projection, you don't need the intermediate step:
public class Result
{
public string Id { get; set; }
public string CustomerEmailAddress { get; set; }
public string EntrepreneurEmailAddress { get; set; }
}
You don't have to nest this class in the index if you don't want to. It doesn't matter either way. You can query either with:
var items = session.Query<Result, OrdersApprovedBroadcastingData>();
Or with
var items = session.Query<OrderDocument, OrdersApprovedBroadcastingData>().As<Result>();
Though, with the first way, the convention tends to be to nest the result class, so really it would be
var items = session.Query<OrderDocument.Result, OrdersApprovedBroadcastingData>();
Note in the index map, I am not including any properties at all. None are required for what you asked. However, if you want to add a Where or OrderBy clause to your query, any fields you might want to filter or sort on should be put in there.
One last thing - the convention you're using of OrderDocument, CustomerDocument, EntrepreneurDocument, is a bit strange. The usual convention is just Order, Customer, Entrepreneur. Think of your documents as the persisted form of the entities themselves. The convention you are using will work, it's just not the one usually used.

Using fluent-nhibernate is it possible to automap a Value Object(s) inside an Entity?

I'm using Sharp Architecture and have a number of situations where Value Objects are used in an Entity. Here is an obvious simple example:
public class Person : Entity
{
protected Person(){}
public Person(string personName)
{
this.PersonName = personName;
}
public virtual string PersonName { get; protected set;}
public virtual StreetAddress MailingAddress { get; set; }
}
public class StreetAddress : ValueObject
{
protected StreetAddress(){}
public StreetAddress(string address1, string address2, string city, string state, string postalCode, string country )
{
this.Address1 = address1;
this.Address2 = address2;
this.City = city;
this.State = state;
this.PostalCode = postalCode;
this.Country = country;
}
public virtual string Address1 { get; protected set; }
public virtual string Address2 { get; protected set; }
public virtual string City { get; protected set; }
public virtual string State { get; protected set; }
public virtual string PostalCode { get; protected set; }
public virtual string Country { get; protected set; }
}
This of course throws: An association from the table Person refers to an unmapped class: Project.Domain.StreetAddress
because the the AutoPersistenceModelGenerator only includes classes with type IEntityWithTypedId<>. Its not clear how Sharp Architecture expects this common condition to be implemented. Does this have to be handled with a bazillion overrides?
You could change the GetSetup() method in AutoPersistenceModelGenerator to something like:
private Action<AutoMappingExpressions> GetSetup()
{
return c =>
{
c.IsComponentType = type => type.BaseType == typeof (ValueObject);
};
}
I'll try to get the blogpost I saw covering this posted for credit.
You would want to map this as a component. You can use the mapping overrides in Fluent NHibernate to accomplish this.
I agree with Alec. I would map this as a component.
For more information on that, see this SO question:
AutoMapping a Composite Element in Fluent Nhibernate
There, you'll also find info on how to map a collection of composite elements.

Specification Pattern for Querying against DataBase using NHibernate

How Do You Implement Specification Pattern for querying database using NHibernate?(without LINQ to NHibernate).I read a lot about Specification Pattern but most of them was about Validation and Querying Memory collection objects.
Best method as far as I know using DetachedCriteria in Specification Interface like this.
interface ISpecification<T> {
bool IsSatisfiedBy(T object);
DetachedCriteria CreateCriteria();
}
Is there any alternative or better way to do this?
This is not nessary better, but can be an alternative
interface ISpecification<T>
{
bool IsSatisfiedBy(T object);
Expression<Func<T, bool>> Predicate { get; }
}
Easy to use over linq (to nhibernate) and memory-collections.
I implemented this using a simple extension method and specification pattern, works for System.Linq.IQueryable lists.
public interface IFilter<in T>
{
bool MatchFilter(T o);
}
public static class FilterExtension
{
public static IQueryable<T> Filter<T>(this IQueryable<T> query, IFilter<T> filter)
{
return query.Where(x => filter.MatchFilter(x));
}
}
Simple example classes and IFilter implementation:
public class Organization
{
public string Name { get; set; }
public string Code { get; set; }
public Address Address { get; set; }
public Organization(string name, string code, string city, string country)
{
Name = name;
Code = code;
Address = new Address(city, country);
}
}
public class Address
{
public Address(string city, string country)
{
City = city;
Country = country;
}
public string City { get; set; }
public string Country { get; set; }
}
public class GenericOrganizationFilter : IFilter<Organization>
{
public string FilterString { get; set; }
public GenericOrganizationFilter(string filterString)
{
FilterString = filterString;
}
public bool MatchFilter(Organization o)
{
return
(o.Name != null && o.Name.Contains(FilterString)) ||
(o.Code != null && o.Code.Contains(FilterString)) ||
(o.Address != null && o.Address.City != null && o.Address.City.Contains(FilterString)) ||
(o.Address != null && o.Address.Country != null && o.Address.Country.Contains(FilterString));
}
}
Usage:
IFilter<Organization> filter = new GenericOrganizationFilter("search string");
//Assuming queryable is an instance of IQueryable<Organization>.
IQueryable<Organization> filtered = queryable.Filter(filter);