Relationships in Habanero - c#

I have been trying to write some generic code to create an xml package of Habanero business objects. The code can currently handle compostion relationships, but I need to add the Association relationships manually. Is there any way to add association relationsips that don't have a composite reverse relationship in a more generic way.
This is how the composition relationships are added
private static void AddRelatedCompositionObjects(Package package, IBusinessObject businessObject)
{
businessObject.Relationships
.Where(rel => rel.RelationshipType == RelationshipType.Composition)
.Where(rel => rel is IMultipleRelationship)
.Select(rel => (IMultipleRelationship)rel)
.ForEach(rel => rel.BusinessObjectCollection
.AsEnumerable<IBusinessObject>()
//.ForEach(package.Add));
.ForEach(bo => BuildPackage(package, bo)));
businessObject.Relationships
.Where(rel => rel.RelationshipType == RelationshipType.Composition)
.Where(rel => rel is ISingleRelationship)
.Select(rel => (ISingleRelationship)rel)
//.ForEach(rel => package.Add(rel.GetRelatedObject()));
.ForEach(rel => BuildPackage(package, rel.GetRelatedObject()));
}
And then I manually add the association relationships
var package = new Package();
foreach (var returnDelivery in returnDeliveries)
{
package.Add(returnDelivery);
if (returnDelivery != null)
{
var materials = returnDelivery.DeliveryItems.Select(item => item.Material).Distinct();
materials.ToList().ForEach(material =>
{
package.Add(material);
material.EWTMaterials.ForEach(package.Add);
});
package.Add(returnDelivery.Customer);
}
}

First thing to realise is that
1) Habanero does not require you to have a reverse relationship defined. Although if you are generating your class definitions from Firestarter you will have one.
I have stolen this sample snippet from the ClassDefValidator in Habanero.BO so it might not be exactly what you want and could certainly be generalised into the architecture.
What this code snipped does is get the reverse relationshipDef for a relationshipDef
this code is in Habanero.BO.ClassDefValidator
CheckRelationshipsForAClassDef method if you look here you will see code to get the relatedClassDef. It should be pretty easy to convert this into something you need.
If you have any problems then give me a shout.
if (!HasReverseRelationship(relationshipDef)) return;
string reverseRelationshipName = relationshipDef.ReverseRelationshipName;
if (!relatedClassDef.RelationshipDefCol.Contains(reverseRelationshipName))
{
throw new InvalidXmlDefinitionException
(string.Format
("The relationship '{0}' could not be loaded for because the reverse relationship '{1}' defined for class '{2}' is not defined as a relationship for class '{2}'. Please check your ClassDefs.xml or fix in Firestarter.",
relationshipDef.RelationshipName, reverseRelationshipName, relatedClassDef.ClassNameFull));
}
var reverseRelationshipDef = relatedClassDef.RelationshipDefCol[reverseRelationshipName];
Brett

Related

Updating EF entities based on deep JSON data

I have a data structure which looks something like this: foo 1:* bar 1:* baz
It could look something like this when passed to the client:
{
id: 1,
bar: [{
id: 1,
baz: []
},
{
id: 2,
baz: [{
id: 1
}]
}
]
}
In my UI, this is represented by a tree structure, where the user can update/add/remove items on all levels.
My question is, when the user has made modifications and I'm sending the altered data back to the server, how should I perform the EF database update? My initial thought was to implement dirty tracking on the client side, and make use of the dirty flag on the server in order to know what to update. Or maybe EF can be smart enough to do an incremental update itself?
Unfortunately EF provides little if no help for such scenario.
The change tracker works well in connected scenarios, but working with disconnected entities has been totally left out for the develpers using the EF. The provided context methods for manipulating entity state can handle simplified scenarios with primitive data, but does not play well with related data.
The general way to handle it is to load the existing data (icluding related) from the database, then detect and apply the add/updates/deletes yourself. But accounting for all related data (navigation property) types (one-to-many (owned), many-to-one (associated), many-to-many etc), plus the hard way to work with EF6 metadata makes the generic solution extremely hard.
The only attempt to address the issue generically AFAIK is the GraphDiff package. Applying the modifications with that package in your scenario is simple as that:
using RefactorThis.GraphDiff;
IEnumerable<Foo> foos = ...;
using (var db = new YourDbContext())
{
foreach (var foo in foos)
{
db.UpdateGraph(foo, fooMap =>
fooMap.OwnedCollection(f => f.Bars, barMap =>
barMap.OwnedCollection(b => b.Bazs)));
}
db.SaveChanges();
}
See Introducing GraphDiff for Entity Framework Code First - Allowing automated updates of a graph of detached entities for more info about the problem and how the package is addressing the different aspects of it.
The drawback is that the package is no more supported by the author, and also there is no support for EF Core in case you decide to port from EF6 (working with disconnected entities in EF Core has some improvements, but still doesn't offer a general out of the box solution).
But implementing correctly the update manually even for specific model is a real pain. Just for comparison, the most condensed equivalent of the above UpdateGraph method for 3 simple entities having only primitive and collection type navigation properties will look something like this:
db.Configuration.AutoDetectChangesEnabled = false;
var fooIds = foos.Where(f => f.Id != 0).Select(f => f.Id).ToList();
var oldFoos = db.Foos
.Where(f => fooIds.Contains(f.Id))
.Include(f => f.Bars.Select(b => b.Bazs))
.ToDictionary(f => f.Id);
foreach (var foo in foos)
{
Foo dbFoo;
if (!oldFoos.TryGetValue(foo.Id, out dbFoo))
{
dbFoo = db.Foos.Create();
dbFoo.Bars = new List<Bar>();
db.Foos.Add(dbFoo);
}
db.Entry(dbFoo).CurrentValues.SetValues(foo);
var oldBars = dbFoo.Bars.ToDictionary(b => b.Id);
foreach (var bar in foo.Bars)
{
Bar dbBar;
if (!oldBars.TryGetValue(bar.Id, out dbBar))
{
dbBar = db.Bars.Create();
dbBar.Bazs = new List<Baz>();
db.Bars.Add(dbBar);
dbFoo.Bars.Add(dbBar);
}
else
{
oldBars.Remove(bar.Id);
}
db.Entry(dbBar).CurrentValues.SetValues(bar);
var oldBazs = dbBar.Bazs.ToDictionary(b => b.Id);
foreach (var baz in bar.Bazs)
{
Baz dbBaz;
if (!oldBazs.TryGetValue(baz.Id, out dbBaz))
{
dbBaz = db.Bazs.Create();
db.Bazs.Add(dbBaz);
dbBar.Bazs.Add(dbBaz);
}
else
{
oldBazs.Remove(baz.Id);
}
db.Entry(dbBaz).CurrentValues.SetValues(baz);
}
db.Bazs.RemoveRange(oldBazs.Values);
}
db.Bars.RemoveRange(oldBars.Values);
}
db.Configuration.AutoDetectChangesEnabled = true;

Filter "base query" for slightly different results

I am trying to query a database using Entity Framework and I need to make several slightly different queries on the same set of tables. There are a load of navigation properties I need to add and it seems logical to me that I should be able to define the "base query" (i.e the one with all the navigation properties" and then further filter this as required and execute the query.
Some code may help explain further. This is what I am calling my "base query"
private static IEnumerable<HelpdeskTicket> GetAll()
{
IEnumerable<HelpdeskTicket> Tickets;
using (ItManagement_Entities db = new ItManagement_Entities())
{
Tickets = db.HelpdeskTickets.Include("CreatedByPerson")
.Include("HelpdeskCategory")
.Include("HelpdeskPriority")
.Include("HelpdeskStatus");
}
return Tickets;
}
As an example, some of the queries I need to perform are open tickets, recently closed tickets, my tickets, yada yada yada.
My thinking is to have methods similar to the following to do the filtering bit I need without having to define all the .Include()'s again.
public static List<HelpdeskTicketModel> GetAllTickets()
{
List<HelpdeskTicketModel> Tickets = new List<HelpdeskTicketModel>();
GetAll().OrderByDescending(t => t.TicketId)
.ToList()
.ForEach(t => Tickets.Add(HelpdeskTicketModel.Map(t)));
return Tickets;
}
public static List<HelpdeskTicketModel> GetRecentlyClosedTickets()
{
List<HelpdeskTicketModel> Tickets = new List<HelpdeskTicketModel>();
GetAll().Where(t => t.HelpdeskStatus.IsClosedStatus)
.OrderByDescending(t => t.ClosedTime)
.ToList()
.ForEach(t => Tickets.Add(HelpdeskTicketModel.Map(t)));
return Tickets;
}
//And so on...
When I try this I get a System.InvalidOperationException exception thrown complaining that The operation cannot be completed because the DbContext has been disposed, which makes sense really because my query was in a different context, in the GetAll method.
Question is, how do I go about doing what I want?
You may try something similar to the Template Method pattern, where each particular method calls some base, private methods that do the common work and then each one adds its particular bits of the query. Something like that may come handy as a starting point:
// Here you define common parts applicable to all methods, or at least shared among some of them
private static IQueryable<HelpdeskTicket> BuildBaseQuery(this IQueryable<HelpdeskTicket> query)
{
return query.Include("CreatedByPerson")
.Include("HelpdeskCategory")
.Include("HelpdeskPriority")
.Include("HelpdeskStatus");
}
// Here are the particular methods, they create a query, call helper methods for the common bits and add their specifics
public static List<HelpdeskTicketModel> GetAllTickets()
{
List<HelpdeskTicketModel> Tickets = new List<HelpdeskTicketModel>();
using (ItManagement_Entities db = new ItManagement_Entities())
{
Tickets = db.HelpdeskTickets.BuildBaseQuery()
.OrderByDescending(t => t.TicketId)
.ToList()
.ForEach(t => Tickets.Add(HelpdeskTicketModel.Map(t)));
}
return Tickets;
}

Nhibernate : Map all decimals with the same precision and scale

I understand that in NHibernate, using mapping by code, I can specify the precision and scale of a decimal property like so:
Property(
x => x.Dollars,
m =>
{
m.Precision(9);
m.Scale(6);
}
);
That is nice, but I was wondering if there was a way that I could easily map ALL of the decimal properties in all of the classes in an easy way. It seems kind of crazy that I would have to go through all of my mappings and update each of them by hand. Does anyone know how this can be achieved ?
Use the BeforeMapProperty on the ModelMapper:-
var mapper = new ModelMapper();
mapper.BeforeMapProperty += (inspector, member, customizer) => {
if (member.LocalMember.GetPropertyOrFieldType() == typeof (decimal))
{
customizer.Precision(9);
customizer.Scale(6);
}
};
The only other thing to add is remove all occurrences of:-
m => { m.Precision(9); m.Scale(6); }
from your mapping classes as these will override your convention set in BeforeMapProperty unless you have other decimals that have different scales or precisions.
You could write a UserType. The advantage is that you could easily distinguish different types of decimals (you most probably don't want to have the same precision for all decimals).
Property(
x => x.Dollars,
m => m.Type<MoneyUserType>()
);
You have some effort to put this into all monetary properties, but then you have a more readable and self descriptive mapping definition.
A similar solution (syntactically), but easier to implement, is to write an extension method which sets up the precision.
Property(
x => x.Dollars,
m => m.MapMoney()
);
public static void MapMoney(this IPropertyMapper mapper)
{
m.Precision(9);
m.Scale(6);
}
Same here: it makes the mapping definition more self descriptive.
(Yes I know that you don't want to change all the files, but I still recommend to put this information into the mapping files, because it is more explicit what the decimal actually is. It is very easy to change the mapping for all Money properties, but keep the Amount properties. For the completely implicit solution, read on.)
Alternatively you could use mapping conventions. These is very powerful. You still can overwrite the precision in the mapping file, which provides great flexibility.
mapper.BeforeMapProperty += MapperOnBeforeMapProperty;
private void MapperOnBeforeMapProperty(
IModelInspector modelInspector,
PropertyPath member,
IPropertyMapper propertyCustomizer)
{
Type propertyType;
// requires some more condition to check if it is a property mapping or field mapping
propertyType = (PropertyInfo)member.LocalMember.PropertyType;
if (propertyType == typeof(decimal))
{
propertyCustomizer.Precision(9);
propertyCustomizer.Scale(6);
}
}
It is also possible to put the user type into the mapping convention, as a default.
Can you use FluentNHibernate? It enables you to apply conventions with as much flexibility as you need. See here: https://github.com/jagregory/fluent-nhibernate/wiki/Conventions and here: http://marcinobel.com/index.php/fluent-nhibernate-conventions-examples/ where it has this particular example:
public class StringColumnLengthConvention
    : IPropertyConvention, IPropertyConventionAcceptance
{
    public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
    {
        criteria.Expect(x => x.Type == typeof(string))
            .Expect(x => x.Length == 0);
    }
 
    public void Apply(IPropertyInstance instance)
    {
        instance.Length(100);
    }
}
This looks like you could easily adapt to map all decimals like he does with strings.

Is there a way to find all Entities that have had their relationships deleted?

I am trying to not have my Business Logic know the inner workings of my Data Layer and vica versa.
But Entity Framework is making that hard. I can insert into a collection (in my Business Layer) without a reference to the ObjectContext:
order.Containers.Add(new Container { ContainerId = containerId, Order = order });
And that saves fine when it comes time to do a SaveChanges() in the Data Layer.
But to delete an item from a collection I need a reference to the ObjectContext. (I am case #1 in this guide to deleting EF Entities.) If I just do this:
delContainers.ForEach(container => order.Containers.Remove(container));
Then when I call SaveChanges() I get an exception telling me that I need to delete the object as well as the reference.
So, my options as I see it are:
To pass a delegate to my Business Logic that will call the Entity Framework ObjectContext Delete method.
Or (I am hoping) find a way to get all entities that have had their reference deleted and actually delete them. (Right before calling SaveChanges() in my data layer.)
Does anyone know a way to do that?
UPDATE:
I tried this:
// Add an event when Save Changes is called
this.ObjectContext.SavingChanges += OnSavingChanges;
...
void OnSavingChanges(object sender, EventArgs e)
{
var objectStateEntries = ObjectContext.ObjectStateManager
.GetObjectStateEntries(EntityState.Deleted);
foreach (var objectStateEntry in objectStateEntries)
{
if (objectStateEntry.IsRelationship)
{
// Find some way to delete the related entity
}
}
}
But none even though I deleted a relationship, the set of deleted items is empty.
(I tried viewing all the items too and my relationship is not in there. Clearly there is something fundamental that I don't get about ObjectStateManager.)
The correct solution for EF is point 3. from the linked article. It means propagating FK to principal entity into PK for dependent entity. This will form something called identifying relation which automatically deletes dependent entity when it is removed from the parent entity.
If you don't want to change your model and still want to achieve that in persistence ignorant way you probably can but it will work only for independent associations. Some initial implementation which works at least for my simple tested solution:
public partial class YourObjectContext
{
public override int SaveChanges(SaveOptions options)
{
foreach (ObjectStateEntry relationEntry in ObjectStateManager
.GetObjectStateEntries(EntityState.Deleted)
.Where(e => e.IsRelationship))
{
var entry = GetEntityEntryFromRelation(relationEntry, 0);
// Find representation of the relation
IRelatedEnd relatedEnd = entry.RelationshipManager
.GetAllRelatedEnds()
.First(r => r.RelationshipSet == relationEntry.EntitySet);
RelationshipType relationshipType = relatedEnd.RelationshipSet.ElementType;
if (!SkipDeletion(relationshipType))
{
// Now we know that model is inconsistent and entity on many side must be deleted
if (!(relatedEnd is EntityReference)) // related end is many side
{
entry = GetEntityEntryFromRelation(relationEntry, 1);
}
if (entry.State != EntityState.Deleted)
{
context.DeleteObject(entry.Entity);
}
}
}
return base.SaveChanges();
}
private ObjectStateEntry GetEntityEntryFromRelation(ObjectStateEntry relationEntry, int index)
{
var firstKey = (EntityKey) relationEntry.OriginalValues[index];
ObjectStateEntry entry = ObjectStateManager.GetObjectStateEntry(firstKey);
return entry;
}
private bool SkipDeletion(RelationshipType relationshipType)
{
return
// Many-to-many
relationshipType.RelationshipEndMembers.All(
r => r.RelationshipMultiplicity == RelationshipMultiplicity.Many) ||
// ZeroOrOne-to-many
relationshipType.RelationshipEndMembers.Any(
r => r.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
}
}
To make it work your entities must be enabled for dynamic change tracking (all properties must be virtual and entity must be proxied) or you must manually call DetectChanges.
In case of foreign key associations the situation will be probably much worse because you will not find any deleted relation in the state manager. You will have to track changes to collections or keys manually and compare them to find discrepancies (I'm not sure how to do it in generic way) Foreign key association IMHO requires the identifying relation. Using FK properties already means that you included additional persistence dependency into your model.
One way is to write a change handler in your data layer:
private void ContainersChanged(object sender,
CollectionChangeEventArgs e)
{
// Check for a related reference being removed.
if (e.Action == CollectionChangeAction.Remove)
{
Context.DeleteObject(e.Element);
}
}
There are many places you can wire this up -- in your object's constructor or repository get or SavingChanges or wherever:
entity.Containers.AssociationChanged += new CollectionChangeEventHandler(ContainersChanged);
Now you can remove the association from elsewhere and it will "cascade" to the entity.

How to save an updated many-to-many collection on detached Entity Framework 4.1 POCO entity

For the last few days I'm trying to properly update my POCO entities. More specific, it's many-to-many relationship collections.
I've three database tables:
Author - 1..n - AuthorBooks - n..1 - Books.
Translates to two POCO entities:
An Author entity with a Books collection and Book entity with a Authors collection.
Case
When I have one active DbContext, retrieve a Book entity, add an Author and call SaveChanges(), the changes are properly send to the database. All fine so far.
However I've a desktop application with limited DbContext lifetime, as displayed in code fragments below.
public Book GetBook(int id)
{
using (var context = new LibariesContext())
{
return context.Books
.Include(b => b.Authors)
.AsNoTracking()
.Single(b => b.BookId == id);
}
}
public Author GetAuthor(int id)
{
using (var context = new LibariesContext())
{
return context.Authors
.AsNoTracking()
.Single(a => a.AuthorId == id);
}
}
A simplified example of various of my business logic methods, wraps it together:
public void BusinessLogicMethods()
{
Book book = GetBook(id: 1);
Author author = GetAuthor(id: 1);
book.Name = "New book title";
book.Authors.Add(author);
SaveBook(book);
}
public void SaveBook(Book book)
{
using (var context = new LibariesContext())
{
context.Entry(book).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
}
Unfortunately the only thing that is really saved here, is the name of the book. The new author was not saved, neither was an Exception thrown.
Questions
What's the best way to save the collection of a detached entity?
Any workaround for this issue?
I'm new to EF 4.1 too, and if I understand your question correctly, I think I ran into this nonsense over the weekend. After trying every approach under the sun to get the entries to update, I found a mantra here on SO (I can't find it any more) and turned it into a generic extension method. So now, after the line:
book.Authors.Add(author);
I would add the line:
context.UpdateManyToMany(book, b => b.Authors)
You might need to restructure your code to make this happen.
Anyway... here's the extension method I wrote. Let me know if it works (no guarantees!!!)
public static void UpdateManyToMany<TSingle, TMany>(
this DbContext ctx,
TSingle localItem,
Func<TSingle, ICollection<TMany>> collectionSelector)
where TSingle : class
where TMany : class
{
DbSet<TSingle> localItemDbSet = ctx.Set(typeof(TSingle)).Cast<TSingle>();
DbSet<TMany> manyItemDbSet = ctx.Set(typeof(TMany)).Cast<TMany>();
ObjectContext objectContext = ((IObjectContextAdapter) ctx).ObjectContext;
ObjectSet<TSingle> tempSet = objectContext.CreateObjectSet<TSingle>();
IEnumerable<string> localItemKeyNames = tempSet.EntitySet.ElementType.KeyMembers.Select(k => k.Name);
var localItemKeysArray = localItemKeyNames.Select(kn => typeof(TSingle).GetProperty(kn).GetValue(localItem, null));
localItemDbSet.Load();
TSingle dbVerOfLocalItem = localItemDbSet.Find(localItemKeysArray.ToArray());
IEnumerable<TMany> localCol = collectionSelector(localItem)?? Enumerable.Empty<TMany>();
ICollection<TMany> dbColl = collectionSelector(dbVerOfLocalItem);
dbColl.Clear();
ObjectSet<TMany> tempSet1 = objectContext.CreateObjectSet<TMany>();
IEnumerable<string> collectionKeyNames = tempSet1.EntitySet.ElementType.KeyMembers.Select(k => k.Name);
var selectedDbCats = localCol
.Select(c => collectionKeyNames.Select(kn => typeof (TMany).GetProperty(kn).GetValue(c, null)).ToArray())
.Select(manyItemDbSet.Find);
foreach (TMany xx in selectedDbCats)
{
dbColl.Add(xx);
}
ctx.Entry(dbVerOfLocalItem).CurrentValues.SetValues(localItem);
}
I came across this question when I was attempting to solve the same problem. In the end I took a different approach which seems to be working. I had to end up exposing a "state" property on the entities and have the calling context set the state of the entities within the object graph.
This reduces the overhead on the web service/data context side to have to determine what's changed, given that the graph could be populated by any number of query permeation. There's also a NuGet package called GraphDiff which might work for you as well(details in the link below).
Anyhow, full details here: http://sanderstechnology.com/2013/solving-the-detached-many-to-many-problem-with-the-entity-framework/12505/

Categories

Resources