I am migrating from google datastore api to objectify(i have used datastore at compute engine and migrating to 6.0a1 objectify). In previous version i used this code to query users:
KeyFactory userKeyFactory = datastore.newKeyFactory()
.addAncestors(PathElement.of("UserList", "default"))
.setKind(USER_KIND);
//...save entity
Query<Entity> query = Query.newEntityQueryBuilder()
.setKind(USER_KIND)
.setFilter(PropertyFilter.hasAncestor(datastore.newKeyFactory().setKind("UserList").newKey("default")))
.build();
QueryResults<Entity> queryResults = datastore.run(query);
List<User> result = new ArrayList<>();
queryResults.forEachRemaining(entity -> result.add(transformUser(entity)));
return result;
Now i am trying to make same query with ancestor. However objectify don't work with PathElement. So following code:
ofy().load().type(User.class).ancestor(PathElement.of("UserList", "default")).list()
fails with
java.lang.IllegalArgumentException: No class 'com.google.cloud.datastore.PathElement' was registered
at com.googlecode.objectify.impl.Registrar.getMetadataSafe(Registrar.java:115)
at com.googlecode.objectify.impl.Keys.getMetadataSafe(Keys.java:56)
at com.googlecode.objectify.impl.Keys.getMetadataSafe(Keys.java:65)
at com.googlecode.objectify.impl.Keys.rawKeyOf(Keys.java:47)
at com.googlecode.objectify.impl.Keys.anythingToRawKey(Keys.java:117)
at com.googlecode.objectify.impl.QueryImpl.setAncestor(QueryImpl.java:203)
at com.googlecode.objectify.impl.SimpleQueryImpl.ancestor(SimpleQueryImpl.java:69)
at com.googlecode.objectify.impl.LoadTypeImpl.ancestor(LoadTypeImpl.java:23)
What is proper way to use PathElement with objectify? I see i can create com.googlecode.objectify.Key and pass it as ancestor but it require class, but i don't have UserList class (it is small application, all entities related to single group).
I tried to use this code:
ofy().load().type(User.class).ancestor(datastore.newKeyFactory().setKind("UserList").newKey("default")).list()
Now it fails that User don't have field with #Parent annotation. Here is stacktrace:
com.googlecode.objectify.LoadException: Error loading Key{projectId=projectId, namespace=, path=[PathElement{kind=UserList, id=null, name=default}, PathElement{kind=User, id=1, name=null}]}: Loaded Entity has parent but com.package.model.User has no #Parent
at com.googlecode.objectify.impl.EntityMetadata.load(EntityMetadata.java:84)
at com.googlecode.objectify.impl.LoadEngine.load(LoadEngine.java:187)
at com.googlecode.objectify.impl.LoadEngine$1.nowUncached(LoadEngine.java:145)
at com.googlecode.objectify.impl.LoadEngine$1.nowUncached(LoadEngine.java:131)
at com.googlecode.objectify.util.ResultCache.now(ResultCache.java:30)
at com.googlecode.objectify.impl.Round$1.nowUncached(Round.java:66)
at com.googlecode.objectify.util.ResultCache.now(ResultCache.java:30)
at com.googlecode.objectify.impl.HybridQueryResults.lambda$load$1(HybridQueryResults.java:87)
at com.google.common.collect.Iterators$5.transform(Iterators.java:757)
at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:48)
at com.google.common.collect.MultitransformedIterator.next(MultitransformedIterator.java:66)
at com.google.common.collect.Iterators$4.computeNext(Iterators.java:623)
at com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:145)
at com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:140)
at com.googlecode.objectify.impl.HybridQueryResults.hasNext(HybridQueryResults.java:92)
at com.googlecode.objectify.util.IteratorFirstResult.nowUncached(IteratorFirstResult.java:31)
at com.googlecode.objectify.util.ResultCache.now(ResultCache.java:30)
at com.googlecode.objectify.LoadResult.now(LoadResult.java:25)
at com.package.UserObjectifyRepository.getUserByEmail(UserObjectifyRepository.java:43)
...
Caused by: java.lang.IllegalStateException: Loaded Entity has parent but com.package.model.User has no #Parent
at com.googlecode.objectify.impl.KeyMetadata.setKey(KeyMetadata.java:142)
at com.googlecode.objectify.impl.KeyMetadata.setKey(KeyMetadata.java:122)
at com.googlecode.objectify.impl.KeyPopulator.load(KeyPopulator.java:24)
at com.googlecode.objectify.impl.translate.ClassPopulator.load(ClassPopulator.java:118)
at com.googlecode.objectify.impl.translate.ClassTranslator.loadSafe(ClassTranslator.java:109)
at com.googlecode.objectify.impl.translate.NullSafeTranslator.load(NullSafeTranslator.java:21)
at com.googlecode.objectify.impl.EntityMetadata.load(EntityMetadata.java:80)
.. 125 more
I suppose correct way to fix this is to have parent field in my User entity, something like this:
#Parent
private UserList userList;
But i haven't such entity "UserList", i need ancestor just to make query with strong consistency.
UPD: Error is gone if i adding this code:
import com.google.cloud.datastore.Key;
#Parent
private Key userList;
Is it proper way to make consistent query?
The #Parent annotation requires a key.
You just have to create a key, so it can understand that Parent/Child relationship.
Related
Background
I have a problem with being able to query child data in a sample Corda application, demonstrating how to persisnt hierarchical data using QueryableState.
For reference:
https://github.com/corda/samples-kotlin/tree/master/Features/queryableState-carinsurance
https://www.corda.net/blog/persisting-corda-states-in-custom-database-tables-using-queryablestate/
The car insurance sample demonstrates how to set up:
a One-to-One relationship between PersistentInsurance and PersistentVehicle
a One-to-Many relationship between PersistentInsurance and PersistentClaim
Problem
What the car insurance sample doesn't demonstrate is how to query that data. Building vault queries from PersistentInsurance is fairly trivial as we can use VaultCustomQueryCriteria to build custom query expressions over the properties of PersistentInsurance, however the same is not true for the child tables in the hierarchy. This is because PersistentInsurance extends PersistentState, whereas PersistentVehicle and PersistentClaim don't.
For Reference, the underlying type hierarchy for PersistentState is as follows:
StatePersistable > DirectStatePersistable > PersistentState
interface StatePersistable
interface DirectStatePersistable : StatePersistable {
val stateRef: PersistentStateRef?
}
class PersistentState(#EmbeddedId override var stateRef: PersistentStateRef? = null) : DirectStatePersistable
data class PersistentStateRef(
#Suppress("MagicNumber") // column width
#Column(name = "transaction_id", length = 144, nullable = false)
var txId: String,
#Column(name = "output_index", nullable = false)
var index: Int
) : Serializable {
constructor(stateRef: StateRef) : this(stateRef.txhash.toString(), stateRef.index)
}
Going back to the car insurance sample, we can't use VaultCustomQueryCriteria for PersistentVehicle or PersistentClaim as VaultCustomQueryCriteria::expression has a generic constraint on StatePersistable:
data class VaultCustomQueryCriteria<L : StatePersistable>(
val expression: CriteriaExpression<L, Boolean>,
...
) : CommonQueryCriteria() { ... }
What I've Tried
Implementing StatePersistable on PersistentVehicle and/or PersistentClaim causes the following error when you create a VaultCustomQueryCriteria using any of the properties from those classes:
net.corda.core.node.services.VaultQueryException: Parsing error: Unable to locate Attribute with the the given name [stateRef] on this ManagedType [...$PersistentClaim]
Implementing DirectStatePersistable or PersistentState on PersistentVehicle and/or PersistentClaim causes an internal error when finalizing the transaction. The flow hangs and eventually times out. I wasn't able to determine exactly where the error occurs as the code is buried inside internal implementations for underlying Corda services.
What Does Work (In a Limited Capacity)
I'm aware that we can execute custom SQL queries; for example:
val sqlQuery = buildString {
appendln("SELECT TRANSACTION_ID, OUTPUT_INDEX")
appendln("FROM CLAIM_DETAIL")
appendln("WHERE claimNumber = '123'")
}
val stateRefs = serviceHub.jdbcSession().executeCaseInsensitiveQuery(sqlQuery).map {
val txId = SecureHash.parse(it.getString("TRANSACTION_ID")
val index = it.getInt("OUTPUT_INDEX")
StateRef(txId, index)
}.toList().toBlocking().first()
VaultQueryCriteria(stateRefs = stateRefs)
Needless to say, that's hideous compared to:
VaultCustomQueryCriteria(PersistentClaim::claimNumber.equal("123"))
As for why it works in a limited capacity, this works fine for queries inside the node, which access to the service hub, but doesn't work over RPC as there's no access to a jdbcSession.
So, any ideas (if it's at all possible), on how we can improve the query capability of child tables?
#Matthew Layton I have put your concerns to the engineering team. The team has looked into this.
To address/fix this issue, requires lot of platform changes.
For time being I would suggest you to manage with using native queries. We are planning to prioritise this issue. I will keep you posted on this.
Using Breeze, what is the simplest way to populate a GUID key when an entity is created?
I'll assume that your entity is configured such that the client is responsible for setting the Guid key for new entities. That's the default for the Guid key of an Entity Framework Code First entity; it is as if the key property were adorned with [DatabaseGenerated(DatabaseGeneratedOption.None)]
The obvious approach is to set the key after creating the entity and before adding it to the manager, e.g.:
function createFoo() {
var foo = fooType.createEntity();
foo.id(breeze.core.getUuid()); // Knockout implementation
manager.addEntity(foo);
}
This may be all you ever need.
On the other hand, you may find that you're creating new Foos in many places and for some strange reason you can't use the createFoo function. You certainly don't want to repeat that code.
You can extend the Foo entity type with id-setting behavior after which you'd be able to write:
function createFoo() {
var foo = fooType.createEntity(); // foo.id is set for you
manager.addEntity(foo);
}
There are two approaches to consider - custom constructor and type initializer; both are described in "Extending Entities"
Constructor
You can initialize the key inside a custom constructor. Breeze calls the constructor both when you create the entity and when it materializes a queried entity. Breeze will replace the initial key value when materializing.
Here's an example that assumes the Knockout model library.
function Foo() {
foo.id(breeze.core.getUuid()); // using KO
}
// one way to get the MetadataStore
var store = manager.metadataStore;
// register the ctor with the Foo type
store.registerEntityTypeCtor("Foo", Foo);
Pretty simple. The only downside is that Breeze will generate a Guid every time it makes an entity, whether creating a new one or materializing one from a query. It's wasted effort during materialization but so what? Well, I suppose that might become a performance issue although I wouldn't assume so until I had measured it.
Initializer
Suppose you measured and the repeated Guid generation is a serious problem (really?). You could set the key in a type initializer instead and only call the Guid generator when creating a new entity.
Breeze calls a type initializer after the entity has been created or materialized from query just before returning that entity to the application. Clearly you don't want to overwrite a materialized key from the database so you'll test the key value to make sure it's not real (i.e. to make sure you're fixing a created entity) before assigning it. Here's an example.
function fooInitializer(foo) {
var emptyGuid = "00000000-0000-0000-0000-000000000000";
if (foo.id() !=== emptyGuid) {
foo.id(breeze.core.getUuid());
}
}
var store = manager.metadataStore;
// register the initializer; no ctor in this example
store.registerEntityTypeCtor("Foo", function(){}, fooInitializer);
Assuming you have a Guid surrogate Key on all your entities like we have in our case, you could code a createInstance factory that does the following in a very generic approach:
function createInstance(breezeEntityManager, typeName) {
var keyProperty = breezeEntityManager.metadataStore.getEntityType(typeName, false).dataProperties.filter(function (p) {
return p.isPartOfKey;
})[0];
var config = {};
config[keyProperty.name] = breeze.core.getUuid();
return breezeEntityManager.createEntity(typeName, config);
}
This way, you won't have to create an initializer for all your entities.
I am using POCO classes on an EF4 CTP5 project and I am having trouble deleting child properties. Here's my example (hopefully not too long).
Relevant Portions of the Tour Class
public partial class Tour
{
public Guid TourId { get; private set; }
protected virtual List<Agent> _agents { get; set; }
public void AddAgent(Agent agent)
{
_agents.Add(agent);
}
public void RemoveAgent(Guid agentId)
{
var a = Agents.Single(x => x.AgentId == agentId);
_agents.Remove(Agents.Single(x => x.AgentId == agentId));
}
}
Command Handler
public class DeleteAgentCommandHandler : ICommandHandler<DeleteAgentCommand>
{
private readonly IRepository<Core.Domain.Tour> _repository;
private readonly IUnitOfWork _unitOfWork;
public DeleteAgentCommandHandler(
IRepository<Core.Domain.Tour> repository,
IUnitOfWork unitOfWork
)
{
_repository = repository;
_unitOfWork = unitOfWork;
}
public void Receive(DeleteAgentCommand command)
{
var tour = _repository.GetById(command.TourId);
tour.RemoveAgent(command.AgentId);
// The following line just ends up calling
// DbContext.SaveChanges(); on the current context.
_unitOfWork.Commit();
}
}
Here's the error that I get when my UnitOfWork calls DbContext.SaveChanges()
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
This is happening because EF wont just automatically delete the an Agent entity from the database just because it has been removed from the Agents collection in my Tour class.
I need to explicitly call dbContext.Agents.DeleteObject(a);, but my problem is, I don't have access to the dbContext from within my POCO.
Is there any way to handle this scenario?
With your current architecture I am afraid you need to feed your DeleteAgentCommandHandler with a second repository (IRepository<Core.Domain.Agent>, I guess) and then call something like Delete(command.AgentId) on that second repository.
Or you could extend your IUnitOfWork to be a factory of repositories, so the interface would get an additional method like T CreateRepository<T>() which allows you to pull any instance of your generic repository from the unit of work. (Then you only need to inject IUnitOfWork into the DeleteAgentCommandHandler, and not the repositories anymore.)
Or stay away from generic repositories in your business/UI layer. If Agent is completely dependent on Tour it doesn't need to have a repository at all. A non-generic ITourRepository could have methods to handle the case of removing an agent from a tour in the database layer appropriately.
This does seem like something that should work. I've found this post which suggests this feature is being investigated for future versions:
http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/58a31f34-9d2c-498d-aff3-fc96988a3ddc/
I've also found another post (somewhere - unfortunately I lost it) which suggested adding the parent entity's key to the child entity in your DbContext OnModelCreating method like this:
modelBuilder.Entity<Agent>()
.HasKey(AgentId)
.HasKey(TourId);
Currently this throws an exception at runtime using code-first, although I have got this working when using an EDMX file by hacking the XAML to include the parent key in the store data model as well as the conceptual data model. I think this difference in behaviour is because in the case of the EDMX file, EF trusts that the store metadata it holds is accurate, whereas code-first checks the database to see whether it's model matches.
Another way which may work although I haven't yet tried it yet, is to include the parent key as a compound key in the child table so that code-first is happy. Obviously changing the database or hacking the XAML are both less than ideal and workarounds at best.
My scenario:
This is an ASP.NET 4.0 web app programmed via C#
I implement a repository pattern. My repositorys all share the same ObjectContext, which is stored in httpContext.Items. Each repository creates a new ObjectSet of type E. Heres some code from my repository:
public class Repository<E> : IRepository<E>, IDisposable
where E : class
{
private DataModelContainer _context = ContextHelper<DataModelContainer>.GetCurrentContext();
private IObjectSet<E> _objectSet;
private IObjectSet<E> objectSet
{
get
{
if (_objectSet == null)
{
_objectSet = this._context.CreateObjectSet<E>();
}
return _objectSet;
}
}
public IQueryable<E> GetQuery()
{
return objectSet;
}
Lets say I have 2 repositorys, 1 for states and 1 for countrys and want to create a linq query against both. Note that I use POCO classes with the entity framework. State and Country are 2 of these POCO classes.
Repository stateRepo = new Repository<State>();
Repository countryRepo = new Repository<Country>();
IEnumerable<State> states = (from s in _stateRepo.GetQuery()
join c in _countryRepo.GetQuery() on s.countryID equals c.countryID
select s).ToList();
Debug.WriteLine(states.First().Country.country)
essentially, I want to retrieve the state and the related country entity. The query only returns the state data... and I get a null argument exception on the Debug.WriteLine
LazyLoading is disabled in my .edmx... thats the way I want it.
You're doing a join without retrieving anything from it. There are multiple solutions to your problem:
Use Include to load the dependent entities: from s in ((ObjectSet<State>) _stateRepo.GetQuery).Include("Country"). The problem with this approach is that you should expose the ObjectSet directly rather than as a IQueryable if you want to avoid casting.
Use context.LoadProperty(states.First(), s => s.Country) to explicitly load the Country from the database for a given state.
Select both entities in the query: from s in ... join c ... select new { s, c }. You won't be able to access directly the state's Country property but you have it in the anonymous type.
Enable lazy loading.
Your repository implementation is very similar to mine, especially the way you are storing the ObjectContext. It works fine for me, so I don't think it's a conceptual problem.
Try using a static objectcontext (no wrapper) just to see if that fixes the problem. Perhaps there is a bug in your ContextHelper which causes your context to get disposed and recreated.
Im trying to get my head around attaching an entity with a related entity to a new context when I want to update the entity.
I have a Person Table (Generalised to Personnel), which has a LanguageID field. This field is linked as a FK via the EF to another table Language with LanguageID as the primary key (1-M). I need to update a particular Persons language preference, however, the relationship seems to remain linked to the old context as i get a "Object cannot be referenced by multiple instances of IEntityChangeTracker" error on the line marked below. Is there any way to attach the Language entity to the new context as a relationship of the Personnel (Person) entity???
The entities were not detached in the orginal GetPersonnel() method which uses an .Include() method to return the PreferredLanguage
PreferredLanguage is the NavigationProperty name on the Person table...
public static void UpdateUser(Personnel originalUser, Personnel newUser )
{
using (AdminModel TheModel = new AdminModel())
{
((IEntityWithChangeTracker)originalUser).SetChangeTracker(null);
((IEntityWithChangeTracker)originalUser.PreferredLanguage).SetChangeTracker(null);
TheModel.Attach(originalUser);--Error Line
TheModel.ApplyPropertyChanges("Person", newUser);
TheModel.SaveChanges();
}
}
Thanks
Sean
To avoid these sort of problems you should make GetPersonnel() do a NoTracking query.
I.e.
ctx.Person.MergeOption = MergeOption.NoTracking;
// and then query as per normal.
This way you can get a graph of connected entities (assuming you use .Include()) that is NOT attached. Note this won't work if you try to manually detach entities, because doing so schreds your graph.
Hope this helps
Alex