How to create an extension to deal with multiple core data entities - core-data

I have two core data entities named Car and Owner, both, obviously NSManagedObject and as all core data entities, all are #Observable by default.
I have created a class where I was observing one of these entities, something like:
class RadioControlModel {
#ObservedObject var carEntity:Car
// ... bla bla
init(_ carEntity:Car, _ name:String) {
self.carEntity = readCarEntityWith(name)
}
}
this class is the model of a radio control that allows the user to switch the state of a boolean value of the Car entity.
Now I need to do the same to the Owner entity, that is, to use this class to change a boolean value of this class but the init is tied to Car. How do I declare this as generic so RadioControlModel can accept any core data entity, not just of type Car.
My problem here is to do so and continue to have the variable #Observable, that is, responding to changes.

Try this:
class RadioControlModel<T: NSManagedObject> {
#ObservedObject var carEntity:T
// ... bla bla
init(_ carEntity:T, _ name:String) {
self.carEntity = readCarEntityWith(name)
}
}

Related

How to reload a row of SwiftUI Core Data-backed list if object properties change?

I have a standard SwiftUI list setup, powered by Core Data FetchRequest.
struct SomeView: View {
var container: Container
var myObjects: FetchRequest<MyObject>
init(container: Container) {
let predicate : NSPredicate = NSPredicate(format: "container = %#", container)
self.container = container
self.myObjects = FetchRequest<MyObject>(entity: MyObject.entity(), sortDescriptors: [NSSortDescriptor(key: "date", ascending: true)], predicate: predicate)
}
var body: some View {
VStack(spacing: 0.0) {
List(myObjects.wrappedValue, id: \.uniqueIdentifier) { myObject in
rowView(for: myObject, from: self.myObjects.wrappedValue)
}
}
}
}
Everything works well when items are added and deleted. RowView returns a view that presents different content based on various properties of myObject.
Problem: when I modify a particular myObject elsewhere in the app (change one of its properties), and save the associated Core Data ManagedObjectContext, the List row representing that item is not updated/refreshed in the UI.
Possibly a cause for this is that I am updating my Core Data object by setting a property, that in turn sets another property. Maybe the associated signaling doesn’t reach the right place, and I should emit more notifications here.
Code in MyObject. ObjectType is an enum, typeValue is int32 backing this, that actually gets stored in CD database.
var type: ObjectType {
get {
return ObjectType(rawValue: typeValue)!
}
set {
self.typeValue = newValue.rawValue
}
}
How do I cause a list row to update when the backing Core Data object is modified and saved elsewhere in the app?
I finally figured this out on my own. The fix was not in the list, but further down the stack, in RowView.
RowView code was such:
struct RowView: View {
var myObject: MyObject
// Other code to render body etc
}
When doing this, the RowView works as expected, but it treats myObject as immutable. Any changes to myObject don’t cause a view redraw.
The one-keyword fix is to add #ObservedObject to the declaration:
struct RowView: View {
#ObservedObject var myObject: MyObject
}
It now works as expected, and any updates to MyObject cause a redraw.

Building a NSManagedObjectModel from several models

There are several reasons why somebody wants to merge multiple NSManagedObjectModel's. If you search the web, all responses are that it is not possible or that it is only possible for two unrelated entities that share one or more relationships. See this and this link for example.
However with a bit or more work it is (I think) possible to merge NSManagedObjectModels, even if the entities are related (as in parent-child) or if the attributes are spread out across multiple models.
Though it will not show as readily in the Xcode model editor and out-of-box transitions (probably) won't work.
In the answer below my observations about core data and my code on merging several models. If you find any bugs or have suggestions for improvements, please respond here.
Some things I noticed:
Copying a NSPropertyDescription (attribute, relationship) copies all its values, but not the entity to which it belongs. Same for the destinationEntity and inverseRelationship.
Thus a copied NSPropertyDescription should be added to an entity. As a result, all the children entities of that entity automatically get the property as well.
Copying a NSEntityDescription does not include the parent entity. So the tree (of NSManagedObjectEntity) has to rebuild manually.
If you set the parent of an entity, that (child) entity will immediately and automatically inherit all its parent properties. In other words when you ask an entity for its attributes, this entity already knows about all its attributes. It will not first query its parent. (reasonable assumption)
Adding entities to a model fills in the destination entities and inverse relationship descriptions of the relationsDescriptions of the added entities.
if you do not set the name of any entity or property before using it, core data will complain. That is the copy by name instead of value aspect.
Adding a property to an entity which already has a property with the same name (either from itself or inherited from its ancestor) will make core data complain.
This translates into the following code:
extension NSPropertyDescription
{
var isPlaceholder : Bool { return self.userInfo?["isPlaceholder"] != nil }
}
extension NSEntityDescription
{
var isPlaceholder : Bool { return self.userInfo?["isPlaceholder"] != nil }
}
func mergeModels(models: [NSManagedObjectModel]) -> NSManagedObjectModel?
{
var entities : [String : NSEntityDescription] = [:]
//support functions
let makeEntity : String -> NSEntityDescription = { entityName in
let newEntity = NSEntityDescription()
entities[entityName] = newEntity
newEntity.name = entityName
return newEntity
}
let setParent : (String, NSEntityDescription) -> () = { parentName, child in
if let parent = entities[parentName]
{
parent.subentities.append(child)
}
else //parent has not yet been encountered, so generate it
{
let newParentEntity = makeEntity(parentName)
newParentEntity.subentities.append(child)
}
}
//rebuild model: generate new description for each entity and add non-placeholder properties
for model in models
{
for entity in model.entities
{
guard let entityName = entity.name else { fatalError() }
let mergedEntity = entities[entityName] ?? makeEntity(entityName)
//set entity properties
if !entity.isPlaceholder
{
mergedEntity.abstract = entity.abstract
mergedEntity.managedObjectClassName = entity.managedObjectClassName
}
//set parent, if any
if mergedEntity.superentity == nil, //no parent set
let parentName = entity.superentity?.name //but parent is required
{
setParent(parentName, mergedEntity)
}
//set properties
for property in entity.properties
{
if property.isPlaceholder ||
mergedEntity.properties.contains({$0.name == property.name})
{ continue }
let newProperty = property.copy() as! NSPropertyDescription
mergedEntity.properties.append(newProperty)
}
}
}
//generate final model
let mergedModel = NSManagedObjectModel()
mergedModel.entities = Array(entities.values) //sets the destination entity and inverse relationship descriptions
return mergedModel
}
In the managedObjectModel (xcode editor) set the "placeholder" flag in the user info dictionary of the entity and/or the property.
The model generation can be refined by setting additional keys in the user info dictionary to specify which model has the prime entity/attribute/relationship (settings) and appropriately adjusting this code fragment.
However, if you can avoid using multiple models then avoid it. Your life will be much simpler by sticking to the standard single Model approach.
[Disclaimer: as far as I can tell, this code should work. No guarantees though.]
The NSManagedObjectModel class has the following factory methods / constructors
class func mergedModel(from: [Bundle]?)
class func mergedModel(from: [Bundle]?, forStoreMetadata: [String : Any])
init?(byMerging: [NSManagedObjectModel]?)
init?(byMerging: [NSManagedObjectModel], forStoreMetadata: [String : Any])
The optional forStoreMetadata attribute allows to specify the models' version.
see https://developer.apple.com/documentation/coredata/nsmanagedobjectmodel
(I suspect these methods not being available at the time the op asked & answered the question.)

Persisting an Entity that is a part of an Aggregate

Consider we have a BankCard Entity that is a part of Client Aggregate. Client may want to cancel her BankCard
class CancellBankCardCommandHandler
{
public function Execute(CancelBankCardCommand $command)
{
$client = $this->_repository->get($command->clienId);
$bankCard = $client->getBankCard($command->bankCardId);
$bankCard->clientCancelsBankCard();
$this->_repository->add($client);
}
}
class BankCard implements Entity
{
// constructor and some other methods ...
public function clientCancelsBankCard()
{
$this->apply(new BankCardWasCancelled($this->id);
}
}
class Client implements AggregateRoot
{
protected $_bankCards;
public function getBankCard($bankCardId)
{
if (!array_key_exists($bankCardId, $this->_bankCards) {
throw new DomainException('Bank card is not found!');
}
return $this->_bankCard[$bankCardId]);
}
}
Finally we have some domain repository instance which is reponsible for storing Aggregates.
class ClientRepository implements DomainRepository
{
// methods omitted
public function add($clientAggregate)
{
// here we somehow need to store BankCardWasCancelled event
// which is a part of BankCard Entity
}
}
My question is whether AggregateRoot responsible for tracking its Entities' events or not. Is it possible to get events of an Entity which is a part of an Aggregate from within its Aggregate or not?
How to actually persist the Client with all changes made to the bank card saving its consistency?
I would say the aggregate as a whole is responsible for tracking the changes that happened to it. Mechanically, that could be "distributed" among the aggregate root entity and any other entities within the aggregate or the aggregate root entity as the sole recorder or some external unit of work. Your choice, really. Don't get too hung up on the mechanics. Different languages/paradigms, different ways of implementing all this. If something happens to a child entity, just consider it a change part of the aggregate and record accordingly.

How to dynamically create collections of derived objects?

This question may appear to have been answered before but I have been unable to find exactly what I need. Here is my situation:
// Base class
interface IAnimal {};
public abstract class Animal : IAnimal{}
// Derived classes
interface IDog {}
public class Dog : Animal, IDog { }
interface ICat { }
public class Cat : Animal, ICat { }
interface ITiger { }
public class Tiger : Animal, ITiger { }
interface ILion { }
public class Lion : Animal, ILion { }
// Collection Classes
interface IPets { }
public class Pets
{
IDog dog = new Dog();
ICat cat = new Cat();
}
interface ICircus { }
public class Circus
{
ITiger tiger = new Tiger();
ILion lion = new Lion();
}
I would like to create the collections at run time in an generic Event class by reading in a list animals from xml that would make up the collection. What would be the correct way to accomplish this?
Thanks in advance.
This is kind of an answer to my own question. Maybe this will help others.
I chose a very generic example to illustrate my situation because I have uses for this in many places in Windows Forms, XNA and Silverlight that are all very different.
When I used the Activator, I found out that it assumes the executing assembly. My method is in a library so I had to load a different assembly. Next I had to make sure that I had the right namespace. My base class is in a library and the derived classes are in another namespace so this will require refactoring to properly create the list.
Another problem I found was that the Activator assumes a constructor with no parameters. In my test case all my derived classes are XNA game components with a parameter of type Game.
Have to do some refactoring to test out the interfaces and how the game objects are to interact.
Will be back to this list when I have something further.
Does this sort of example help? (It's from some of my code I happened to have handy.) The key point here is the use of reflection in Activator.CreateInstance(...).
public static List<dynamic> LoadChildEntities(XElement entityElt)
{
var children = new List<dynamic>();
foreach(XElement childElt in entityElt.Elements("entity"))
{
// Look up the C# type of the child entity.
string childTypename = "MyNamespace." + Convert.ToString(childElt.Attribute("type").Value);
Type childType = Type.GetType(childTypename);
if(childType != null)
{
// Construct the child entity and add it to the list.
children.Add(Activator.CreateInstance(childType, childElt));
}
else
{
throw new InvalidOperationException("No such class: " + childTypename);
}
}
return children;
}
If you want a list of IAnimal instead, it wouldn't be too tricky to change.

Is this the correct way to instantiate an object with dependencies from within a Domain Model?

I'm trying to avoid ending up with an anaemic Domain Model, so I'm attempting to keep as much logic as possible within the domain model itself. I have a method called AddIngredient, which needs to add a new KeyedObject to my Recipe Aggregate.
As the Domain Models themselves are meant to be devoid of repositories, I'm getting the ingredient via a business rule class:
public class Recipe : AggregateObject
{
public void AddIngredient(int ingId, double quantity)
{
GetIngredientMessage message = new GetIngredientMessage();
message.IngredientId = ingId;
GetIngredient handler = ServiceLocator.Factory.Resolve<GetIngredient>();
Ingredient ingredient = handler.Execute(message);
Ingredients.Add(new OriginalIngredient()
{
Ingredient = ingredient,
Quantity = quantity
});
}
}
As you can see, I'm using a line the line ServiceLocator.Factory.Resolve<GetIngredient>(); to obtain my GetIngredient business rule class. GetIngredient is a simple command handler that looks like the following:
public class GetIngredient : ICommandHandler<Ingredient, GetIngredientMessage>
{
private readonly IIngredientRepository _ingredientRepository;
public GetIngredient(IIngredientRepository ingredientRepository)
{
_ingredientRepository = ingredientRepository;
}
}
I assign my IoC factory class to the ServiceLocator.Factory, so the Domain has the ability to use its own interfaces, without seeing the concrete class implementation:
ServiceLocator.Factory = new IoCFactory();
I'm pretty sure I'm doing something wrong as it all feels a little bit bodge-like.
Can anyone spot anything blatantly wrong?
Is there a more appropriate way to instantiate a business rule handler such as GetIngredient without a static reference to my IoC Factory?
I suggest you introduce another layer into the design -- the Application layer. This layer responsibility would be to translate commands (either explicitly encapsulated in command objects or passed implicitly as int ingId, double quantity) into domain model invocations (Recipe.AddIngredient).
By doing so you'll move the responsibility of finding an ingredient by its id to a layer above domain, where you can safely make use of repositories directly without introducing unwanted coupling. The transformed solution would look something like this:
public class ApplicationLayer
{
private readonly IRecipeRepository _recipeRepository;
private readonly IIngredientRepository _ingredientRepository;
/*
* This would be called by IoC container when resolving Application layer class.
* Repositories would be injected by interfacy so there would be no coupling to
* concrete classes.
*/
public ApplicationLayer(IRecipeRepository recipeRepository, IIngredientRepository ingredientRepository)
{
_recipeRepository = recipeRepository;
_ingredientRepository = ingredientRepository;
}
public void AddIngredient(int recipeId, int ingId, double quantity)
{
var recipe = _recipeRepository.FindById(recipeId);
var ingredient = _ingredientRepository.FindById(ingId);
recipe.AddIngredient(ingredient, quantity);
}
}
And the now simplified Recipe class would look something like this:
public class Recipe : AggregateObject
{
public void AddIngredient(Ingredient ingredient, double quantity)
{
Ingredients.Add(new OriginalIngredient()
{
Ingredient = ingredient,
Quantity = quantity
});
}
}
Hope that helps.

Resources