polonskyg
polonskyg

Reputation: 4319

Composite pattern with Entity Framework 4.1 code first

I need to represent a composite pattern with Entity Framework code first. How can this be accomplished? I've read a post about using a visitor pattern, but I think it should be done easily and less complex with Fluent API, but I don't know how.

It saves correctly the data in the database but when I try to load it back again It brings wrong data.

var components = from p in ctx.LayerComponents.Include("ComponentsLayer").Include("Component") select p;

foreach (var p in components)
{
    Trace.WriteLine("------------------------------------------------------------------------------------------------");
    p.Apply();
}

This is my model so far:

public abstract class LayerComponents
{
    public LayerComponents()
    {
        Components = new List<Component>();
    }

    [Key]
    public int Id { get; set; }
    public string Description { get; set; }

    public ICollection<Component> Components { get; set; }
    public abstract void Apply();
    public abstract void AddLayer(LayerComponents component);
    public abstract void RemoveLayer(LayerComponents component);
    public abstract void AddComponent(Component component);
}

public class CompositeLayerComponents : LayerComponents
{
    public ICollection<LayerComponents> ComponentsLayer { get; set; }
    public int? ParentID { get; set; }
    public CompositeLayerComponents Parent { get; set; }

    public CompositeLayerComponents()
    {
        ComponentsLayer = new List<LayerComponents>();
    }

    public override void ApplyComponents()
    {
        foreach (LayerComponents lp in ComponentsLayer)
        {
            lp.ApplyComponents();
        }
        Trace.WriteLine("Inside: " + Description);
        foreach (var p in Components)
        {
            Trace.WriteLine("       Applying component: " + p.Key.ToString() + "-" + p.Value.ToString());
        }
        Trace.WriteLine("Done Applying Components in " + Description + Environment.NewLine);
    }

    public override void AddLayer(LayerComponents component)
    {
        ComponentsLayer.Add(component);
    }

    public override void RemoveLayer(LayerComponents component)
    {
        ComponentsLayer.Remove(component);
    }

    public override void AddComponent(Component component)
    {
        this.Components.Add(component);
    }
}

public class LeafLayerComponents : LayerComponents
{
    public override void ApplyComponents()
    {
        Trace.WriteLine("Inside: " + Description);
        foreach (var p in Components)
        {
            Trace.WriteLine("       Applying component: " + p.Key.ToString() + "-" + p.Value.ToString());
        }
        Trace.WriteLine("Done Applying Components in " + Description + Environment.NewLine);
    }

    public override void AddLayer(LayerComponents component)
    {
        throw new Exception("Can't add a layer to the LeafLayerComponents");
    }

    public override void RemoveLayer(LayerComponents component)
    {
        throw new Exception("Can't add a layer to the LeafLayerComponents");
    }

    public override void AddComponent(Component component)
    {
        Components.Add(component);
    }
}

public class Component
{
    [Key]
    public int Id { get; set; }
    [Required]
    protected string Description { get; set; }

    [Required]
    public int KeyValue { get; set; }

    [Required]
    public string Value { get; set; }
    [Required]
    public Guid userId { get; set; }
}

The problem is that when I load the database records, it doesn't map correctly again in memory. For example: If I save this

    root = new CompositeLayerComponents { Description = "root" };

        // Set building preferences
        var buildingComponentsLayer = new CompositeLayerComponents { buildingId = Guid.NewGuid(), Description = "buildingComponents" };
        buildingComponentsLayer.AddComponent(new Component { Key = 0, Value = "Concept2" });
        buildingComponentsLayer.AddComponent(new Component { Key = 1, Value = "1" });
        buildingComponentsLayer.AddComponent(new Component { Key = 2, Value = "true" });

        var floor1LayerComponents = new CompositeLayerComponents { Description = "floor1Components" };
        floor1LayerComponents.AddComponent(new Component() { Key = 0, Value = "Concept1" });
        floor1LayerComponents.AddComponent(new Component() { Key = 1, Value = "2" });
        floor1LayerComponents.AddComponent(new Component() { Key = 2, Value = "true" });

        var floor2LayerComponents = new CompositeLayerComponents { Description = "floor2Components" };
        floor2LayerComponents.AddComponent(new Component() { Key = 0, Value = "Concept1" });
        floor2LayerComponents.AddComponent(new Component() { Key = 1, Value = "2" });
        floor2LayerComponents.AddComponent(new Component() { Key = 2, Value = "false" });

        var officeComponentsLayer = new LeafLayerComponents { Description = "officeComponents" };
        officeComponentsLayer.AddComponent(new Component() { Key = 0, Value = "Concept1" });

        buildingComponentsLayer.AddComponentLayer(floor1LayerComponents);
        floor2LayerComponents.AddComponentLayer(officeComponentsLayer);
        buildingComponentsLayer.AddComponentLayer(floor2LayerComponents);
        root.AddComponentLayer(buildingComponentsLayer);

        ctx.LayerComponents.Add(root);
        ctx.SaveChanges();

It doesn't load one root layer with one buildingLayerComponent inside which has 2 floorLayerComponents and one of that floorLayerComponents has one officeLayerComponent. That's the problem, how to load that hierarchy back again.

Upvotes: 0

Views: 1853

Answers (1)

Ladislav Mrnka
Ladislav Mrnka

Reputation: 364389

It is hard to follow your code because it is even not compilable but if your problem is that it doesn't load whole hierarchy then it is not a problem it is a feature. EF doesn't load relations automatically. You must tell it to do it and in hierarchical structures it is actually very hard.

You can use eager loading:

var root = context.LayerComponents.OfType<CompositeLayerComponsnts>().Include(c => c.ComponentsLayer).Where(c => c.ParentId == null);

This query will load all top level composites and their direct layer components (only single level!).

You can also use lazy loading but all your navigation properties must be marked as virtual. In such case EF will create separate query for loading each navigation property once accessed for the first time. In hierarchical structure this can result in a lot of database queries.

I expect you will have many other problems with your code. For example if you expect that all three layers will use the same components they will not - each component will be added three times and if key represents its primary key it will have different value.

You should probably read little bit about EF and how it works before you start to do complex mapping otherwise you will have a lot of troubles.

Upvotes: 1

Related Questions