How to implement unit of work?

Jul 21, 2014 at 10:53 AM
Hi there,

I am trying to get unitofwork with BrightstarEntityContext, but could not see any Set<T>() property.

Any idea how to implement unit of work with the current context?

Thanks!
Coordinator
Jul 29, 2014 at 12:42 PM
Hi,

Sorry for the delay in getting back on this. I'm not quite sure what you are after here - the context is in essence implementing the unit of work pattern - changes to entities are tracked locally and then passed to the store only when you call SaveChanges. The best way to use this is to create the context with a using statement. That forces you into limiting yourself to code that can sit inside the using block.

If there is something I'm missing, perhaps you can provide an example of what you would like to be able to do ?

Cheers

Kal
Jul 29, 2014 at 1:17 PM
Edited Jul 29, 2014 at 1:18 PM
Hi Kal,

Thanks for your reply.

Sorry, just to clarify, I am looking to implement the unit of work with the repository pattern. By unit of work I mean unit of work within the application that uses the context. So I would like to create (inject) a UnitOfWork object, that is injected with the context.

Firstly, I would like to implement most of the the basic operations in a base repository. So for example, if I have People Collection and Cars collection in the context, I would like to have something like context.GetAll<T>() that I can use in the base repository. Is there something like that which is available within the BrightstarDb framework?

Additionally, when working with .Net entity Framework I could define an IObjectContext interface that has generic operations on Sets, The actual db context implemented the IObjectContext, and so I could just inject it to the repositories and the unitofwork object , without creating explicitly a context object. That's way I could reassure that the unit of work is being applied within the application.

Do you have any suggestion / pointers of how to get this pattern when using BSDB?

Please let me know if you need any more clarifications.

Thanks!
Coordinator
Jul 29, 2014 at 1:51 PM
I think it shouldn't be a problem to use constructor injection for your UnitOfWork object. In fact I would recommend that you do that. Don't use a singleton for the context, just construct it as needed and ensure it is disposed with the UnitOfWork.

To get all instances of a type - there isn't a directly equivalent method on our context object, but it shouldn't be hard to add that if it would help, however that would only give you an IEnumerable<T> and just enumerating all the instances is going to be slow. Alternatively you can implement it by returning the appropriate BrightstarEntityCollection from the context so in your case you would have code like:
if (typeof(T).Equals(typeof(Car)) {
  return _context.Cars;
}
if (typeof(T).Equals(typeof(Person)) {
  return _context.Persons;
}
throw new NoIdeaWhatYouAreTalkingAboutException();
Those collection properties are the ones that are generated by the text template and they are implemented as BrightstarEntityCollection<T> which implements ICollection<T>and IQueryable<T> so there is support for the most basic of generic collection operations plus LINQ for faster selection - does that give you what you need ?

Cheers

Kal
Jul 29, 2014 at 2:15 PM
Edited Aug 4, 2014 at 9:34 AM
Thanks Kal,
Aug 4, 2014 at 2:20 PM
Edited Aug 4, 2014 at 2:49 PM
Hi Kal,

I am implementing the application unit of work. I have extended the context class by creating a partial class, and applied an interface:
public partial class MyEntityContext : IDbContext
    {
        public IEntitySet<T> Set<T>()
        {
            if (typeof(T).Equals(typeof(ICharacter)))
            {
                return (IEntitySet<T>)this.Characters;
            }
            if (typeof(T).Equals(typeof(ICredit)))
            {
                return (IEntitySet<T>)this.Credits;
            }
            if (typeof(T).Equals(typeof(IPerson)))
            {
                return (IEntitySet<T>)this.Persons;
            }
            if (typeof(T).Equals(typeof(IProduction)))
            {
                return (IEntitySet<T>)this.Productions;
            }
            if (typeof(T).Equals(typeof(IRole)))
            {
                return (IEntitySet<T>)this.Roles;
            }

            throw new NotImplementedException();
         
        }
        
    }

    public interface IDbContext: IDisposable
    {
        IEntitySet<T> Set<T>();
        void SaveChanges();

    }
I tried to get it to work with interfaces, so I could use dependency injection. Would the casting of the returned object to IEntitySet<T> be OK, or will it cause enumeration (even though IEntitySet<T> implements IQueryable<T>)?

Thank you!
Coordinator
Aug 4, 2014 at 3:36 PM
Casting to IEntitySet<T> should be OK as this is the wrapper that implements the IQueryable<T> interface, so there shouldn't be any enumeration until you query/enumerate the IEntitySet<T> instance.

Cheers

Kal
Aug 5, 2014 at 9:25 AM
Great, thank you.

Would it be possible to add something like that to the context.tt file? I have added it myself to the context.tt file in my project:
private void WriteGetSets(CodeGenerationTools code, Helper helper, string namespaceName, string contextClassName)
{
    BeginNamespace(code, namespaceName);
#>

    public partial class <#=code.Escape(contextClassName) #> : IDbContext 
    {   
        public IEntitySet<T> Set<T>()
        {
        <#+
            foreach(var i in helper.GetDecoratedInterfaces()) 
            {
        #>
            if (typeof(T).Equals(typeof(<#=i.InterfaceFullName#>)))
            {
                return (IEntitySet<T>)this.<#= code.Escape(i.PluralizedName) #>;
            }
        <#+
            }
        #>
            throw new NotImplementedException();
        }
    }
    
    public interface IDbContext: IDisposable
    {
        IEntitySet<T> Set<T>();
        void SaveChanges();

    }

<#+
    EndNamespace(namespaceName);
}
It simply adds the code in my previous comment within a namespace at the end of the original context class.

It could be a nice feature to have.
Coordinator
Aug 6, 2014 at 11:25 AM
Sounds like an excellent idea.

Added as https://github.com/BrightstarDB/BrightstarDB/issues/129
Coordinator
Aug 6, 2014 at 7:12 PM
I've implemented this as a method on the generated context class. I called the method EntitySet<T>() rather than just Set<T>(), but otherwise it is implemented as you suggested. Thanks for the contribution!
Aug 7, 2014 at 8:42 AM
Great, thank you!
Aug 12, 2014 at 8:50 AM
Hi there, following the above implementation, I have created a IBaseRepository<T> and BaseRepository<T> that use the unit of work:
 public class BaseRepository<T> : IBaseRepository<T> where T :  IEntity
    {
        private readonly IUnitOfWork _unitOfWork;
      
        public BaseRepository(IUnitOfWork unitOfWork)
        {
            if (unitOfWork == null) throw new ArgumentNullException("unitOfWork");
            _unitOfWork = unitOfWork;
        }

        public void Add(T entity)
        {
            _unitOfWork.Context.EntitySet<T>().Add(entity);
        }

        public T Create()
        {
            return _unitOfWork.Context.EntitySet<T>().Create();
        }

        public IEntitySet<T> GetAll()
        {
            return _unitOfWork.Context.EntitySet<T>(); 
        }


        public T GetById(string Id)
        {
            return _unitOfWork.Context.EntitySet<T>().FirstOrDefault(x => Id == x.Id);
        }
    }
Everything works fine except the GetById for which I am getting:

The expression 'Convert([x]).Id' (type: System.Linq.Expressions.MemberExpression) is not supported in a FILTER expression.

However when I access the collection directly, bot through the repository, it works fine:

So with the above two lines of code:
     var e1 = uow.GetRepository<IPerson>().GetAll().FirstOrDefault(x => x.Id == "xxx");
     var e2 = uow.GetRepository<IPerson>().GetById("xxx");
The first one works fine, but for the second one, I get the above error.

Would you know why it is happening and if it can be sorted?

Thank you!
Aug 12, 2014 at 9:29 AM
Edited Aug 12, 2014 at 9:45 AM
Hi there,

For now, to make it work, I have added GetById<T> to the context.tt (similar to the EntitySet<T>)
private void WriteGetById(CodeGenerationTools code, Helper helper, string namespaceName, string contextClassName)
{
#>

    public partial class <#=code.Escape(contextClassName) #> 
    {   
        public T GetById<T>(string id)
        {
        <#+
            foreach(var i in helper.GetDecoratedInterfaces()) 
            {
        #>
            if (typeof(T).Equals(typeof(<#=i.InterfaceFullName#>)))
            {
                return (T)this.<#= code.Escape(i.PluralizedName) #>.FirstOrDefault(x => x.Id == id);
            }
        <#+
            }
        #>
             throw new InvalidOperationException(typeof(T).FullName + " is not a recognized entity interface type.");
        }
    }
    

<#+
}
If that's the only way around it, would it be p[possible to add it to the context.tt as well?

Thanks!
Coordinator
Aug 13, 2014 at 3:03 PM
That is wierd - I would have expected the two statements to be equivalent. I'm not sure why an extra Convert() is thrown in there but maybe its because in one case you use x=> x.Id == "xxx" and in the other you do the comparison the other way around (x=> Id == x.Id). What happens if you change your code for GetById to this:
 return _unitOfWork.Context.EntitySet<T>().FirstOrDefault(x => x.Id.Equals(Id));
Does that still give the same issue ? Note - I don't think that the .Equals() makes a difference, but it would be worth trying both x.Id.Equals(Id) and x.Id == Id.

Cheers

Kal
Aug 14, 2014 at 8:45 AM
Edited Aug 14, 2014 at 8:46 AM
Hi, I tried all of them, and none of them worked.

So when calling the FirstOrDefault outside the repository it works fine:
var e1 = uow.GetRepository<IPerson>().GetAll().FirstOrDefault(x => x.Id == id);
var e2 = uow.GetRepository<IPerson>().GetAll().FirstOrDefault(x => x.Id.Equals(id));;
 
But the GetById() fails whichever way I implement it:
var e3 = uow.GetRepository<IPerson>().GetById(id);

public T GetById(string id)
{
   //return _unitOfWork.Context.EntitySet<T>().FirstOrDefault(x => x.Id == id);
     return _unitOfWork.Context.EntitySet<T>().FirstOrDefault(x => x.Id.Equals(id));
}
Could it be related to the fact that the BaseRepository is a generic class of type T where T is IEntity? So when you call the FirstOrDefault from within the repository it converts the IPerson to IEntity?

Thank you
Coordinator
Aug 14, 2014 at 9:46 AM
It does look like it is related to type conversion issues. I did think that the generic type T should have been resolved to the type you are invoking it with at runtime, because EntitySet<T>() should be returning and IEntitySet<T> instance (so in your case an IEntitySet<IPerson> instance). I'll probably need to spend a bit of time to dig into this. It could be that the conversion required is a trivial thing or even a no-op that just got inserted by the LINQ query processor, in which case it would be possible to create a special case to handle this. Otherwise we could go for extending the generated code in the context further - though I would like to avoid that if possible.
Aug 14, 2014 at 1:31 PM
That's the only thing that is different between them (even though I thought as well it should be resolved correctly).

I know extending the generated code is not the proper way in this case, but for now that the only thing that worked for me.

Thanks for looking into it!
Coordinator
Aug 14, 2014 at 7:09 PM
Hi,

Can you check the code I added in this commit: https://github.com/BrightstarDB/BrightstarDB/commit/7cd0d64177ec9fd1f81c524b9801394fa2abb12b

Ignore the little edit to the MyEntityContext.cs - that was just me playing around to see what other compilable ways we could retrieve the entity set. But if you look at the changes in SimpleContextTests.cs I've added a basic repository and unit of work class and a simple test that just creates an entity and tries to retrieve it by its ID. The test runs fine in my environment, but that is probably because I'm missing some subtlety of the way you are using the EntitySet<T>() method or maybe in your type hierarchy (in the test I used the IBaseEntity -> IDerivedEntity hierarhcy that I created a while ago for testing property inheritance).

If you have some ideas about what may be different that might help me to find a way to reproduce the error. Or if you can code up a simple test case that shows the error and submit a pull request that would be really helpful.

Cheers

Kal
Aug 15, 2014 at 10:15 AM
Hi, That did not work for me either.

It is quite similar to how I have done it. I use interfaces, including IDbContext that applies on MyEntityContext. I also pass T into the repository method Add(T entity), not just using it as a returned type. Also I did constraint T to be of IEntity (my base entity) only (not a class) - but even when I changed these, I still got that error.

I have been playing with the code in the following project: https://github.com/nz-dig/BSD_Test7 (it is a console app).

Thanks!
Coordinator
Aug 17, 2014 at 4:33 PM
Edited Aug 17, 2014 at 4:37 PM
Hi,

Thanks for that - its much easier to look at running code than to try and reproduce from descriptions!

I've taken a first look at the code & I think it might be possible to support what you need just on a partial class without having to extend the .tt file. That would be nice because it would enable you to write the necessary infrastructure for your UoW pattern without having to modify the B* tt file.

However, there is still one issue that is a blocker - that is because you have IEntity declare the Id property and then override it in the derived classes to change the prefix for the identifier. The problem is that when B* introspects the Id property it gets IEntity as being the declaring type and uses the prefix mapping from that interface (i.e. none) which results in an invalid SPARQL query getting generated. In the LINQ it is possible to retrieve the actual type of the item in the member expression (e.g. IPerson) but when you inspect the PropertyInfo for the Id property you still get the declared type as IEntity.

I'd like to see if we can support this because it seems like the way you have declared your types is a valid approach to enabling different identifier prefixes for different types of entity with some common base. It might be possible to change the logic here to use the type of the item rather than the declaring type on the property to resolve Id mapping issues, but I need to check this carefully as it could well introduce some regression bugs that may be tricky to handle later on.

I'll probably have a bit of time to take a look at this in more detail later this week. I'll let you know how I get on.

Cheers

Kal
Aug 18, 2014 at 8:26 AM
Great Kal, thanks for looking into it.