Escolar Documentos
Profissional Documentos
Cultura Documentos
Using the primary key value One way to instantiate the entity in an object is by passing all primary key values to the constructor of the entity class to use:
DataAccessAdapter adapter = new DataAccessAdapter(); CustomerEntity customer = new CustomerEntity("CHOPS");
adapter.FetchEntity(customer);
Using a related entity Another way to instantiate this same entity is via a related entity. The example uses the KeepConnectionOpen feature by passing true to the constructor of the DataAccessAdapter object. The example explicitly closes the connection after the DataAccessAdapter usage is finished.
DataAccessAdapter adapter = new DataAccessAdapter(true); OrderEntity order = new OrderEntity(10254); adapter.FetchEntity(order); order.Customer = (CustomerEntity)adapter.FetchNewEntity(new CustomerEntityFactory(), order.GetRelationInfoCustomer());
}
Polymorphic fetches Imagine the following entity setup: BoardMember entity has a relation (m:1) with CompanyCar. CompanyCar is the root of a TargetPerEntityHierarchy inheritance hierarchy and has two subtypes: FamilyCar and SportsCar. Because BoardMember has the relation with CompanyCar, a field called 'CompanyCar' is created in the BoardMember entity which is mapped onto the m:1 relation BoardMember - CompanyCar.
myBoardMember.GetRelationInfoCompanyCar());
However, 'car' in the example above, can be of a different type. If for example the BoardMember instance in myBoardMember has a FamilyCar as company car set, 'car' is of type FamilyCar. Because the fetch action can result in multiple types, the fetch is called polymorphic. So, in our example, if 'car' is of type FamilyCar, the following code would also be correct:
myBoardMember.GetRelationInfoCompanyCar());
Creating a new / modifying an existing entity
adapter.SaveEntity(customer, true);
Modifying an entity
1. Loading an existing entity in memory, alter one or more fields (not sequenced fields) and call a DataAccessAdapter object's SaveEntity() method
CustomerEntity customer = new CustomerEntity("CHOPS"); DataAccessAdapter adapter = new DataAccessAdapter(true); adapter.FetchEntity(customer); customer.Phone = "(605)555-4321"; adapter.SaveEntity(customer);
adapter.CloseConnection() 2. Create a new entity, set the primary key values (used for filtering), set the IsNew to false, set one or more other fields' values and call a DataAccessAdapter object's SaveEntity() method. This will not alter the PK fields.
CustomerEntity customer = new CustomerEntity(); customer.CustomerID="CHOPS";
adapter.SaveEntity(customer);
3. Via the DataAccessAdapter's UpdateEntitiesDirectly() method, specifying the primary key fields as the filter.
RelationPredicateBucket bucket = new RelationPredicateBucket(); bucket.PredicateExpression.Add(ProductFields.CategoryId == 3); ProductEntity updateValuesProduct = new ProductEntity(); updateValuesProduct.Discontinued=true; DataAccessAdapter adapter = new DataAccessAdapter();
After line 'A', myOrder.CustomerID will be set to "CHOPS", because of the synchronization between the PK of Customer and the FK of Order. At line 'B', the foreign key field CustomerID of Order is changed to a new value, "BLONP". Because the FK field changes, the referenced entity through that FK field, Customer, is dereferenced and myOrder.Customer will return null/Nothing. Because there is no current referenced customer entity, the variable referencedCustomer will be set to null / Nothing at line 'C'.
The opposite is also true: if you set the property which represents a related entity to null (Nothing), the FK field(s) forming this relation will be set to null as well, as shown in the following example:
PrefetchPath2 path = new PrefetchPath2((int)EntityType.OrderEntity); path.Add(OrderEntity.PrefetchPathCustomer); OrderEntity myOrder = new OrderEntity(10254); adapter.FetchEntity(myOrder, path); // A
myOrder.Customer = null;
// B
At line A, the prefetch path loads the related Customer entity together with the Order entity 10254. At line B, this customer is dereferenced. This means that the FK field of order creating this relation, myOrder.CustomerId, will be set to null (Nothing). So if myOrder is saved after this, NULL will be saved in the field Order.CustomerId. Deleting an entity
DataAccessAdapter adapter = new DataAccessAdapter(true); CustomerEntity customer = new CustomerEntity("CHOPS"); adapter.DeleteEntity(customer);
adapter.CloseConnection();
Entity state in distributed systems In distributed environments, you work disconnected: the client holds data and doesn't have a connection with the server for manipulating the data in the client process, it only contacts the service for persistence and data retrieval from the database. To understand the state of an entity object the following explanation could help. Think in these steps: 1. 2. 3. 4. 5. 6. Create containers (entity objects) Add data to/load data in containers (from server for example) Show data in modifiers (forms) Data is modified and collected for persistence Collected data is send to server for persistence Process is ended
After step 6) the state should be considered void. It's up to you to ignore that and keep data around on the client. But as you work disconnected, there is no feedback from the server, so for example if you send an entity from client to server and it is saved there: you won't all of a sudden have an outofsync entity on the client, as that's just a copy of the object on the server. So if you want to keep on working on the client with the data, you have to consider that after step 6) you have to rewind to 1) or 2), unless you know what you can keep (read-only data for example). If you're in 6) and you rewind to 4), you're modifying data which is out of sync with the server. LLBLGen Pro doesn't provide you with a layer which takes care of that, as you should control that yourself, because only then the developer has full control over when what happens. So when you send a UnitOfWork2 object to the server, you have to realize you're in 5) moving to 6) and it's all over for that process. If that's not the case, then you shouldn't move from 4) to 5) there, but wait and persist the data later.
Concurrency control
private class OrderConcurrencyFilterFactory : IConcurrencyPredicateFactory { public IPredicateExpression CreatePredicate( ConcurrencyPredicateType predicateTypeToCreate, object containingEntity) { IPredicateExpression toReturn = new PredicateExpression(); OrderEntity order = (OrderEntity)containingEntity; switch(predicateTypeToCreate) { case ConcurrencyPredicateType.Delete: toReturn.Add(OrderFields.EmployeeID == order.Fields[(int)OrderFieldIndex.EmployeeID].DbValue); break; case ConcurrencyPredicateType.Save: // only for updates toReturn.Add(OrderFields.EmployeeID == order.Fields[(int)OrderFieldIndex.EmployeeID].DbValue); break; } return toReturn; }
}
Adapter contains an advanced concurrency mechanism, in such a way that you can decide how to implement concurrency control in your application. It is often better to schedule concurrency aspects at a high level in your application, however if you are required to check whether a save can take place or not, you can. As does SelfServicing, Adapter allows you to specify a predicate expression object with the SaveEntity() method. This predicate expression is included in the UPDATE query (it's ignored in an INSERT query) so you can specify exactly when a save should take place. Adapter also allows you to implement the interface IConcurrencyPredicateFactory, and instances of that interface can be inserted into entity objects. If such a factory is present inside an entity, SaveEntity() will automatically request a predicate object from that factory to include in the UPDATE query. This way you can still provide concurrency predicates during a recursive save action. Entities, NULL values and defaults
OrderEntity order = new OrderEntity(10254); DataAccessAdapter adapter = new DataAccessAdapter(); adapter.FetchEntity(order); order.ShippingDate = null;
adapter.SaveEntity(order);
To test if a field is currently representing a NULL value, or better: if the entity would be saved now, does the field become NULL in the database, you can use a different method: TestCurrentFieldValueForNull():
// [C#] CustomerEntity customer = new CustomerEntity("CHOPS"); customer.SetNewFieldValue((int)CustomerFieldIndex.ContactTitle, null); customer.TestCurrentFieldValueForNull(CustomerFieldIndex.ContactTitle); // returns true
Extending an entity by intercepting activity calls During the entity's lifecycle and the actions in which the entity participates, various methods of the entity are called, and which might be a good candidate for your own logic to be called as well, for example when the entity is initialized you might want to do your own initialization as well. The entity classes offer a variety of methods for you to override so you can make your code to be called in various situations. These methods start all with On and can be found in the LLBLGen Pro reference manual in the class EntityBase2. The entity classes also offer events for some situations, like the Initializing and Initialized events. If you want to perform a given action when one of these methods are called, you can override them in the generated entity classes, preferably using the methods discussed in Adding your own code to the generated classes. IDataErrorInfo implementation The .NET interface IDataErrorInfo is now implemented on EntityBase. Two methods have been added to the entities: SetEntityError and SetEntityFieldError, which allows external code to set the error of a field and/or entity. If append is set to true with SetEntityFieldError, the error message is appended to an existing message for that field using a semi-colon as separator. Entity field validation, which is triggered by the entity's method SetNewFieldValue() (which is called by a property setter), sets the field error if an exception occurs or when the custom field validator fails. The error message is appended to an existing message.
adapter.FetchEntityCollection(orders, customer.GetRelationInfoOrders());
The entity inside 'customer' is used to construct the filter bucket created by GetRelationInfoOrders() which filters the orders in the persistent storage on the CustomerID field and value "CHOPS".
Adapter does not support lazy loading. All loading of data is by hand. This has the advantage that you can transfer an EntityCollection object to another process/tier and be certain no database connection/logic is necessary or required to work with the data inside the collection. It also ensures no extra data is available to the developer/object that you didn't supply. You can filter on more fields, including filtering on fields in different entities by adjusting the RelationPredicateBucket object. The RelationPredicateBucket object is retrieved from the GetRelationInfo*() methods. You can also construct your own if you want. The EntityCollection object to fill and which is passed to the FetchEntityCollection() method has to contain a valid IEntityFactory2 implementing object. LLBLGen Pro will generate such a factory for each entity. In the example above, customer.Orders is an EntityCollection instance created inside the customer object (and created by the constructor of CustomerEntity) and already contains the valid factory object for OrderEntity objects. If Order is in an inheritance hierarchy, the fetch is polymorphic. This means that if the customer entity, in this case customer "CHOPS", has references to instances of different derived types of Order, every instance in customer.Orders is of the type it represents, which effectively means that not every instance in Orders is of the same type. See for more information about polymorphic fetchs also Polymorphic fetches.