Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DbFunction namespace in base DbContext #13

Open
michaelcsikos opened this issue May 12, 2017 · 11 comments
Open

DbFunction namespace in base DbContext #13

michaelcsikos opened this issue May 12, 2017 · 11 comments

Comments

@michaelcsikos
Copy link

It looks like it's not possible to declare a function in a base DbContext because the namespaceName passed to [DbFunction("DbContextClassName", "FunctionName"] needs to be the name of the subclass.

If the name of the subclass is identical but in a different C# namespace, it works, but that's not always practical. If the names are different, and we pass in the namespaceName of the base DbContext it throws the following exception when using the function:

The specified method 'System.Linq.IQueryable`1[System.String] FunctionName(System.String)' on the type 'Example.BaseDbContextName' cannot be translated into a LINQ to Entities store expression.

Is this something that could be fixed, or is there another way? Thanks for your help.

@moozzyk
Copy link
Owner

moozzyk commented May 12, 2017

I think it should be possible. The first argument is the namespaceName which is the name of the entity container. If you don't know what name to use try using the hint from my post:

methods must have the DbFunctionAttribute whose the first argument is the conceptual container name and the second argument is the function name. The container name is typically the name of the DbContext derived class however if you are unsure you can use the following code snippet to get the name:

Console.WriteLine(
    ((IObjectContextAdapter) ctx).ObjectContext.MetadataWorkspace
        .GetItemCollection(DataSpace.CSpace)
        .GetItems<EntityContainer>()
        .Single()
        .Name);

@michaelcsikos
Copy link
Author

The Name property has a public set but it throws, even when called in the DbContext constructor:

System.InvalidOperationException: The operation cannot be performed because the item is read only.

@moozzyk
Copy link
Owner

moozzyk commented May 12, 2017

You need to read it to know what to put as the namespace name in the attribute, not set it. (Once the model is built it is sealed hence the exception.)

@michaelcsikos
Copy link
Author

Well, that doesn't help, because the subclass will normally have a different name to the base class. The point of having a base class is to share code, but it only works if we don't have to hardcode the namespaceName.

@moozzyk
Copy link
Owner

moozzyk commented May 12, 2017

Oh snap, you are right. Can you describe your scenario a bit more? Why do you have multiple derived contexts and how you use it? Do all of these context connect to the same database? Are you using the results in queries? Can the store function be converted to a query?
The problem here is that the DbFunctionAttribute is an EF concept and this is EF who uses/requires it. The attribute is not touched or interpreted by the convention.

@michaelcsikos
Copy link
Author

I have a product which has a common set of tables, base classes and functionality. There are (currently) two other products which use the common core, and add their own tables with foreign keys to the base tables. Previously, I was using Linq to SQL, but of course the dbml files are automatically generated; it's impossible to share data access code between the projects. So each product had its own dbml, which was a maintenance problem whenever any of the base tables changed.

Think of a ProjectBase table with Id, Number and Name columns. Product Abc adds an AbcProject table, and product Xyz adds an XyzProject table.

Ideally, I want the common core to be responsible for its own data access, but if the common core has its own dbml and the other products have their own dbmls, they are different classes, and different instances at runtime, so it was very difficult to not repeat yourself.

I am migrating to Entity Framework Code First largely so I can define the common tables, stored procedures and functions in a base DbContext. Then it’s reasonably easy to define the data access for the common base classes in one place. The base classes know how to talk to the base DbContext so they can do their own data access, and the other products do their bit, but it’s one instance of the DbContext, so it works great.

@moozzyk
Copy link
Owner

moozzyk commented May 15, 2017

Thanks for sharing your scenario. Unfortunately, I am not sure whether exposing store functions from the base DbContext class can work. @divega - do you have any ideas?

@divega
Copy link

divega commented May 15, 2017

@michaelcsikos @moozzyk A couple of thoughts:

  1. I don't remember the APIs by heart, but isn't there a way to influence the container name generated by code first while still creating the model or in a model convention? I think if it was possible to set it then you would jut write all the functions to use the same and that would solve the problem, correct?
  2. If the above is not possible, then I think enabling this would require a (possibly small) change to EF.
    1. We use attributes to map the methods to functions for convenience, but attributes are quite inflexible. We always knew that it should be possible to have an out-of-band registration mechanism for this functions. The idea would be to have a alternate source of truth that can be used to decide the mapping in places where currently the attribute is being queried for, e.g.: https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/Core/Objects/ELinq/MethodCallTranslator.cs#L64.
    2. A simpler solution would be to have a way to override the namespace name with a different attribute, e.g. there could be a class-level attribute that overrides the namespace for all the functions discovered through that class (regardless of whether the methods are inherited). Again, it would require modifying the mechanism mentioned above to take this new attribute into account.
    3. Change FunctionCallTranslator to allow NamespaceName to be empty/null, and then change the logic at https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/Core/Objects/ELinq/MethodCallTranslator.cs#L633 to have a sensible default, e.g. the C-space container name.

@mondayuk
Copy link

Hi. Did anyone ever get around to solving/bypassing this? I have a similar problem where I used derived classes based on a root DbContext. If I create an instance of the DbContext directly all works well, but if I create an instance of the derived class it stops working. I have tried redefining the functions on the derived class etc, replacing the namespace name for that of the derived class but it doesn't work either.

@moozzyk
Copy link
Owner

moozzyk commented Jul 17, 2018

@mondayuk - no, I have not looked into this.

@michaelcsikos
Copy link
Author

I still use the same class name, EfDbContext, in each project as a workaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants