Xamarin Forms and EntityFramework Attributes compatibility
Asked Answered
S

1

4

I have a client/server solution using C#, WPF, ASP.NET WebAPI and Entity Framework. Client and server clases share the model among his projects. Now I am trying to create a new client, using Xamarin Forms and sharing the model to, but Entity Framework attributes(MaxLength, Index, NotMapped, etc), are not compatible in a PCL. So this are the things that I've tried:

Import Microsoft.EntityFrameworkCore to the PCL Model

As described here, you should be able to use entity framework with Xamarin forms, so I convert the PCL to NetStandard 1.3, and it works, every EntityFramework attribute is allowed. But now the server project is not compatible with that standard and I cannot add packages like prism and Newtonsoft.Json in the model project.

Mock the attributes for Xamarin forms using the bait and switch trick

I've tried the approach described here, based on creating custom attributes in the model PCL, and redefining them in the class libraries. MyClient.Droid and MyClient.UWP redefine the attributes leaving them empty, and MyServer will redefine them with the Entity Framework functionality.

Custom IndexAttribute - Model PCL:

namespace Model.Compatibility
{
    public class IndexAttribute : Attribute
    {
        public IndexAttribute()
        {
        }
    }
}

Custom IndexAttribute - Server side:

[assembly: TypeForwardedToAttribute(typeof(Model.Compatibility.IndexAttribute))]
namespace Model.Compatibility
{
    public class MockedIndexAttribute : System.ComponentModel.DataAnnotations.Schema.IndexAttribute
    {
        public MockedIndexAttribute()
        {
        }
    }
}

I test this aproach calling var attribute = new Model.Compatibility.IndexAttribute();. MockedIndexAttribute constructor is never called.

Create a Shared Project Instead of PCL

This way is a little more messy, but looks like it works. Just creating a new shared project for the model, and using conditional flags like this:

#if !__MOBILE__
[NotMapped, Index]
#endif
public Guid Id { get; set; }

I've not fully deployed this approach at the moment, but if I cannot make none of the first two ways working, I will go with this.

EDIT - Trying to make the "Bait and Switch Attributes" approach work

As @AdamPedley sugested and this thread to, I've redefined IndexAttribute in a new PCL(Xamarin.Compatibility), using the same namespace as the original one:

namespace System.ComponentModel.DataAnnotations.Schema
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public class IndexAttribute : Attribute
    {
        public IndexAttribute() { }
    }
}

Now, my PCL Model includes a reference to Xamarin.Compatibility, so I can use Index attribute in my model properties:

[Index]
public Guid Id { get; set; }

Then, from my Server project, I call the next line of code to check what constructor is called, the custom attribute, or the one defined by EntityFramework:

PropertyInfo prop = typeof(MyClass).GetProperty("Id");
object[] attributes = prop.GetCustomAttributes(true);

The constructor called is the custom one, so it does not work because it have to call to the attribute defined by EntityFramework. Thats is the thing that I don't know, what is the mechanism that make my model's PCL select custom attribute or EF attribute depending on the calling assembly.

I've also added a file in my server project, called TypeForwarding.Net.cs(as sugested here), that contains:

[assembly: TypeForwardedTo(typeof(IndexAttribute))]

But still not working.

Slipperwort answered 6/4, 2017 at 7:30 Comment(11)
What's the problem with the Bait and Switch technique. Is something not working for you? Its the cleanest solution to this issue.Kolyma
@AdamPedley The problem is that I could not make it work. As I've said, when I create an instance of Model.Compatibility.IndexAttribute from a class inside my server side, MockedAttribute constructor is not called, so it is not working. I should be doing something wrong, could you show me an example of the Bait and Switch technique applied to redefining attributes?Slipperwort
The attribute constructor will only be run when something actually gets the attributes for that model and tries to read them. It won't necessarily run just when you call it. Test it by calling typeof(MyModel).GetCustomAttributes(true); on a model that actually has the attribute attached.Kolyma
Ok, I think I understand you. I should only redefine the mocked attribute in the projects that does not have it. The part that I dont understand is: how does the project that has it correctly defined(Server project with EntityFramework installed), in a different assembly? System.ComponentModel.DataAnnotations.Schema.IndexAttribute vs Model.Compatibility.Attribute, isn't there a conflict? or it just prioritises to the System one?Slipperwort
I think you were responding to my previous comment I deleted, sorry, doing way too many things at once :) Read my new one.Kolyma
Yes, I've noticed it. I've followed both comment recomendations also. I'll edit the question explaining the new things that I've tried.Slipperwort
I hit a similar issue to this quite recently. I wanted to use EF and share classes across api and client. Fortunately I also had issues with cyclic dependencies with my database data so I use data transfer objects to copy the data to which is then sent to the client. Some repeated code but this works for my scenario pretty well.Becker
@JamesMundy I have a lot of model classes, so Duplicate is not an option. Thanks anyways.Slipperwort
I believe the EF fluent API is PCL and NetStandard friendly. Thus you can create POCO objects and let the fluent api do the cross platform mappings instead of using attributes. msdn.microsoft.com/en-us/library/jj591617(v=vs.113).aspx (Note: I did this with a project using EF6 and PCL projects to share across MVC / WPF / Mobile)Nebo
Thanks @JonDouglas, I will give it a try.Slipperwort
@JonDouglas EF Fluent API works. Put your comment as an answer and If I dont receive a better response I will mark it as correct.Slipperwort
N
3

I believe the EF fluent API is PCL and NetStandard friendly. Thus you can create POCO objects and let the fluent api do the cross platform mappings instead of using attributes. msdn.microsoft.com/en-us/library/jj591617(v=vs.113).aspx

Note: I did this with a project using EF6 and PCL projects to share across MVC / WPF / Mobile

Nebo answered 25/4, 2017 at 15:31 Comment(2)
would be nice if you shared that core for the OP.Antilogism
What core? If you mean code, there are sufficient examples in the docs or searching on Google. I can't share code sadly as the project was at a previous job a few years back.Nebo

© 2022 - 2024 — McMap. All rights reserved.