I'm using EF4 with POCO objects the 2 tables are as follows
Service
ServiceID,
Name,
StatusID
Status
StatusID,
Name
The POCO objects look like this
Service
ServiceID,
Status,
Name
Status
StatusID,
Name
With Status on the Service object being a Navigation Property and of type Status.
In my Service Repository I have a save method that takes a service objects attaches it to the context and calls save. This works fine for the service, but if the status for that service has been changed it does not get updated. My Save method looks like this
public static void SaveService(Service service)
{
using (var ctx = Context.CreateContext())
{
ctx.AttachModify("Services", service);
ctx.AttachTo("Statuses",service.Status);
ctx.SaveChanges();
}
}
The AttachModify method attaches an object to the context and sets it to modified it looks like this
public void AttachModify(string entitySetName, object entity)
{
if (entity != null)
{
AttachTo(entitySetName, entity);
SetModified(entity);
}
}
public void SetModified(object entity)
{
ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
If I look at a SQL profile its not even including the navigation property in the update for the service table, it never touches the StatusID. Its driving me crazy. Any idea what I need to do to force the Navigation Property to update?
Edit
To give a quick example of the problem heres a quick console app that uses EF with my POCO objects to produce this issue
static void Main(string[] args)
{
Service svc = GetService();
Console.WriteLine("Service : " + svc.Name + " , Status : " + svc.Status.Name);
//Change and save Status
svc.Status = GetStatus("Stopped");
using (var ctx = new TestEFContext())
{
//Status is changed
Console.WriteLine("Service : " + svc.Name + " , Status : " + svc.Status.Name);
ctx.AttachModify("Services", svc);
ctx.AttachTo("Statuses", svc.Status);
ctx.SaveChanges();
}
//Re-fetch service from db and check status
svc = GetService();
//Status is set back to its old value!!!!!!!!
Console.WriteLine("Service : " + svc.Name + " , Status : " + svc.Status.Name);
Console.ReadLine();
}
private static Service GetService()
{
using (var ctx = new TestEFContext())
{
return ctx.Services.Include("Status").FirstOrDefault();
}
}
private static Status GetStatus(string name)
{
using (var ctx = new TestEFContext())
{
return ctx.Statuses.Where(n=>n.Name == name).FirstOrDefault();
}
}
public class Service
{
[DataMember] public int ServiceID { get; set; }
[DataMember] public string Name { get; set; }
[DataMember] public Status Status { get; set; }
}
public class Status
{
[DataMember] public int StatusID { get; set; }
[DataMember] public string Name { get; set; }
}
The reason I'm not holding on to the context is because in the real app I'm trying to use this on its all done in WCF in a disconnected way.
This problem was dragging on so I ended up going for a solution I'm not overlly happy with but I just needed to get it working.
My solution was on save to re-fetch the entities from the DB and use ApplyCurrentValues to update them to match the updated POCO objects.
Based on my example in the question this is the solution I used
static void Main(string[] args)
{
Service svc = GetService();
svc.Status = GetStatus("Stopped");
using (var ctx = new TestEFContext())
{
var svc2 = ctx.Services.Where(s=>s.ServiceID == svc.ServiceID).FirstOrDefault();
svc2.Status = ctx.Statuses.Where(n => n.StatusID == svc.Status.StatusID).FirstOrDefault();
ctx.ApplyCurrentValues("Services", svc);
ctx.SaveChanges();
}
}
I really would rather get it working the way it was coded in the question as I think that is a much neater solution so if anyone can improve on this please do
You must manually set state to Modified for each entity you want to update. Attaching entity to context set its state to Unchanged. Also you don't need to attach status separately. It is already attached with service because AttachTo method attaches whole object graph. You can also try to use Attach instead of AttachTo but I don't think it will be source of the problem.
Related
I'm using the following code in my startup class to prevent errors serializing my entities which may cause circular references, but it is not working.
Why?
public partial class Startup
{
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
// Use Entity Framework Code First to create database tables based on your DbContext
Database.SetInitializer(new MobileServiceInitializer());
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Services.Add(typeof(IExceptionLogger), new AiExceptionLogger());
if (string.IsNullOrEmpty(settings.HostName))
{
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
app.UseWebApi(config);
}
}
According to your description, I created my Azure Mobile App project to test this issue. Based on your Startup.cs, I added my apiController as follows:
[MobileAppController]
public class ValuesController : ApiController
{
[Route("api/values")]
public HttpResponseMessage Get()
{
Department sales = new Department() { Name = "Sales" };
Employee alice = new Employee() { Name = "Alice", Department = sales };
sales.Manager = alice;
return Request.CreateResponse(sales);
}
}
public class Employee
{
public string Name { get; set; }
//[JsonIgnore]
public Department Department { get; set; }
}
public class Department
{
public string Name { get; set; }
public Employee Manager { get; set; }
}
When access this endpoint, I encountered the following XML Circular Object References error:
Note: For a simple way, I removed the XML Formatter via config.Formatters.Remove(config.Formatters.XmlFormatter);. Also, you could refer to the section about preserving object references in XML from Handling Circular Object References.
After I removed XML Formatter, then I encountered the following error about object references loop in JSON:
Then, I followed this Loop Reference handling in Web API code sample, but without luck in the end. Also, I tried to create a new Web API project and found that ReferenceLoopHandling.Ignore could work as expected.
In the end, I found that if I remove the MobileAppController attribute from my
apiController, then it could work as follows:
In summary, I assumed that you could try to ignore the reference attributes with the JsonIgnore for JSON.NET, for more details you could refer to fix 3:Ignore and preserve reference attributes.
Sorry, another multi tenancy post. I can't find a good solution to site, I have read tons of great posts on multi tenancy for ASP MVC but I still need some good advice.
I have an ASP MVC Entity Framework 6 Code First web application. This app has to work for many different clients using a single database for all of them.
I have an entity for all the clients, and each client can have different hosts.
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
...
public ICollection<ClientHost> Hosts { get; set; }
}
public class ClientHost
{
public int ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
}
I have added a column "ClientId" to all the entities I need to filter, so I can separate data from different clients.
public class SomeEntity
{
public int Id { get; set; }
...
public int ClientId { get; set; }
}
First thing I need is, base on the host, retrieve the ClientId to work with.
private static int GetClientId()
{
var currentClient = Convert.ToInt32(HttpRuntime.Cache[CacheClient]);
if (currentClient != null) return currentClient;
lock (Synclock)
{
using (var dataContext = new MyDataContext())
{
var urlHost = HttpContext.Current.Request.Url.Host;
currentClient = dataContext.Clients
.FirstOrDefault(p => p.Hosts.Any(h => h.Name == urlHost));
if (currentClient == null) return null;
HttpRuntime.Cache.Insert(CacheClient, currentClient, null, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(0), CacheItemPriority.Default, null);
return currentClient;
}
}
}
QUESTION 1
As you see I get the clientId from DB and store it in cache, so I don't have to call DB every time I need it.
I don't know if there is a better approach to get the client Id or, better, to store it.
EDIT
After investigation I have created a variable in DbCOntext and initialize it in the Startup.cs file.
public class MyDataContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public static string ClientId { get; set; }
public MyDataContext() : base("MyDataBase") { }
public static MyDataContext Create()
{
return new myDataContext();
}
....
}
In Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
MyDataContext.ClientId = ClientConfiguration.GetCurrentClientId();
ConfigureAuth(app);
}
}
QUESTION 2
Once I have the ClientId, I need to add a filter to every query that needs it. Doing this manually can take you to make many errors or forget to do it in some places.
I need a way that the application can add the filter to all queries automatically (only those entities that need it), so I don't have to worry about a client getting other client's data. Also I need to add the ClientId to all the Insert and Update commands.
I have read about filtering and/or use EF Interceptors, but after reading some posts about that I can't figure out how to do it. Need some help here.
Thanks in advance.
EDIT
In order to solve QUESTION 2 I have followed this great post by Xabikos:
http://xabikos.com/2014/11/17/Create-a-multitenant-application-with-Entity-Framework-Code-First-Part-1/
I have changed it a little bit, since I don't use Users to get the current tenant and instead I use the host. This is part of the program I don't know yet how I'm going to solve but, assuming I already have the ClientId I can add filters to all the queries without realizing that is happening:
I have replaced all the user logic:
private static void SetTenantParameterValue(DbCommand command)
{
if (MyDataContext.ClientId == 0) return;
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = MyDataContext.ClientId;
}
}
Same in all the places...
Than I only have to mark the entities that have to filter with TenantAware, indicating the property. In this case I do in my base class and then apply that base class to all the entities I need.
[TenantAware("ClientId")]
public abstract class ClientEntity : Entity, IClientEntity
{
public int ClientId { get; set; }
public Client Client { get; set; }
}
Here are a couple of things I have done in the past that might help.
Question 1:
I am not a big fan of session as the web is supposed to be stateless. However, it is sometimes necessary. Your approach is reasonable. You could also use cookies as well. What I use are Json Web Tokens (JWT) via my authentication provider (Auth0.com). For each request as it is authenticated, I look for this client id. Here is an example. This is MVC 6 as well. You could do the same type of things w/ cookies.
public class Auth0ClaimsTransformer : IClaimsTransformer
{
private string _accountId = AdminClaimType.AccountId.DefaultValue;
private string _clientId = AdminClaimType.ClientId.DefaultValue;
private string _isActive = AdminClaimType.IsActive.DefaultValue;
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
foreach (var claim in context.Principal.Claims)
{
switch (claim.Type)
{
case "accountId":
_accountId = claim.Value ?? _accountId;
break;
case "clientId":
_clientId = claim.Value ?? _clientId;
break;
case "isActive":
_isActive = claim.Value ?? _isActive;
break;
}
}
((ClaimsIdentity)context.Principal.Identity)
.AddClaims(new Claim[]
{
new Claim(AdminClaimType.AccountId.DisplayName, _accountId),
new Claim(AdminClaimType.ClientId.DisplayName, _clientId),
new Claim(AdminClaimType.IsActive.DisplayName, _isActive)
});
return Task.FromResult(context.Principal);
}
Then in my Startup.cs Configure method I plug in my claims transformer.
app.UseJwtBearerAuthentication(options);
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new Auth0ClaimsTransformer()
});
Next I use a base authentication controller that parses out my claims into properties I can use in my controller.
[Authorize]
[Route("api/admin/[controller]")]
public class BaseAdminController : Controller
{
private long _accountId;
private long _clientId;
private bool _isActive;
protected long AccountId
{
get
{
var claim = GetClaim(AdminClaimType.AccountId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _accountId);
return _accountId;
}
}
public long ClientId
{
get
{
var claim = GetClaim(AdminClaimType.ClientId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _clientId);
return _clientId;
}
}
public bool IsActive
{
get
{
var claim = GetClaim(AdminClaimType.IsActive);
if (claim == null)
return false;
bool.TryParse(claim.Value, out _isActive);
return _isActive;
}
}
public string Auth0UserId
{
get
{
var claim = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
return claim == null ? string.Empty : claim.Value;
}
}
private Claim GetClaim(AdminClaimType claim)
{
return User.Claims.FirstOrDefault(x => x.Type == claim.DisplayName);
}
Finally in my controller it is trivial to extract which tenant is making the call. e.g.
public FooController : BaseController
{
public async Task<IActionResult> Get(int id)
{
var foo = await _fooService.GetMultiTenantFoo(ClientId, id);
return Ok(foo);
}
}
Question 2:
One of the ways I have used in the past is create a BaseMultiTenant class.
public class BaseMultiTenant
{
public int ClientId {get;set;}
public virtual Client Client {get;set;}//if you are using EF
}
public class ClientHost : BaseMultiTenant
{
public string Name {get;set;}
//etc
}
Then simply create an extension method for multi-tenant based entities. I know this doesn't "do it automatically" but it is an easy way to ensure each multi-tenant entity is being called only by its owner.
public static IQueryable<T> WhereMultiTenant<T>(this IQueryable<T> entity, int clientId, Expression<Func<T, bool>> predicate)
where T : BaseMultiTenant
{
return entity.Where(x => x.ClientId == clientId)
.Where(predicate);
}
Then when someone calls for their resource you can:
var clientHost = _myContext.ClientHosts
.WhereMultiTenant(ClientId,
x => x.Name == "foo")
.FirstOrDefault();
Hope this is helpful.
Also found a similar example using an interface.
I have an MVC application that uses Entity Framework 5. In few places I have a code that creates or updates the entities and then have to perform some kind of operations on the updated data. Some of those operations require accessing navigation properties and I can't get them to refresh.
Here's the example (simplified code that I have)
Models
class User : Model
{
public Guid Id { get; set; }
public string Name { get; set; }
}
class Car : Model
{
public Guid Id { get; set; }
public Guid DriverId { get; set; }
public virtual User Driver { get; set; }
[NotMapped]
public string DriverName
{
get { return this.Driver.Name; }
}
}
Controller
public CarController
{
public Create()
{
return this.View();
}
[HttpPost]
public Create(Car car)
{
if (this.ModelState.IsValid)
{
this.Context.Cars.Create(booking);
this.Context.SaveChanges();
// here I need to access some of the resolved nav properties
var test = booking.DriverName;
}
// error handling (I'm removing it in the example as it's not important)
}
}
The example above is for the Create method but I also have the same problem with Update method which is very similar it just takes the object from the context in GET action and stores it using Update method in POST action.
public virtual void Create(TObject obj)
{
return this.DbSet.Add(obj);
}
public virtual void Update(TObject obj)
{
var currentEntry = this.DbSet.Find(obj.Id);
this.Context.Entry(currentEntry).CurrentValues.SetValues(obj);
currentEntry.LastModifiedDate = DateTime.Now;
}
Now I've tried several different approaches that I googled or found on stack but nothing seems to be working for me.
In my latest attempt I've tried forcing a reload after calling SaveChanges method and requerying the data from the database. Here's what I've done.
I've ovewrite the SaveChanges method to refresh object context immediately after save
public int SaveChanges()
{
var rowsNumber = this.Context.SaveChanges();
var objectContext = ((IObjectContextAdapter)this.Context).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, this.Context.Bookings);
return rowsNumber;
}
I've tried getting the updated object data by adding this line of code immediately after SaveChanges call in my HTTP Create and Update actions:
car = this.Context.Cars.Find(car.Id);
Unfortunately the navigation property is still null. How can I properly refresh the DbContext immediately after modifying the data?
EDIT
I forgot to originally mention that I know a workaround but it's ugly and I don't like it. Whenever I use navigation property I can check if it's null and if it is I can manually create new DbContext and update the data. But I'd really like to avoid hacks like this.
class Car : Model
{
[NotMapped]
public string DriverName
{
get
{
if (this.Driver == null)
{
using (var context = new DbContext())
{
this.Driver = this.context.Users.Find(this.DriverId);
}
}
return this.Driver.Name;
}
}
}
The problem is probably due to the fact that the item you are adding to the context is not a proxy with all of the necessary components for lazy loading. Even after calling SaveChanges() the item will not be converted into a proxied instance.
I suggest you try using the DbSet.Create() method and copy across all the values from the entity that you receive over the wire:
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
this.Context.Entry(newEntry).CurrentValues.SetValues(obj);
return newEntry;
}
UPDATE
If SetValues() is giving an issue then I suggest you try automapper to transfer the data from the passed in entity to the created proxy before Adding the new proxy instance to the DbSet. Something like this:
private bool mapCreated = false;
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
if (!mapCreated)
{
Mapper.CreateMap(obj.GetType(), newEntry.GetType());
mapCreated = true;
}
newEntry = Mapper.Map(obj, newEntry);
this.DbSet.Add(newEntry;
return newEntry;
}
I use next workaround: detach entity and load again
public T Reload<T>(T entity) where T : class, IEntityId
{
((IObjectContextAdapter)_dbContext).ObjectContext.Detach(entity);
return _dbContext.Set<T>().FirstOrDefault(x => x.Id == entity.Id);
}
I have this class
public class Status
{
private string status;
public string StatusName
{
get
{
return status;
}
}
private Status (string _status)
{
status = _status;
}
public static readonly Status Open = new Status("Open");
public static readonly Status Closed = new Status("Closed");
}
Using Code First, can I configure EF to load one of the predefined objects (Open, Closed) instead of trying to create a new one?
Or is there a better way to accomplish a similar behavior?
Current EF version does not provide any hook to replace object materialization (unless you download source code an try to implement it yourselves). That means EF will always create its own status and to be able to do that it will also need your Status class to match its rules. I don't know why you need this but if you really need always the same object instances for your statuses you can hack it.
First you need to modify your Status class for EF:
public class Status{
// EF needs access to your property
public string StatusName { get; private set; }
// EF needs parameterless constructor because it will create instances
private Status() {}
private Status (string status) {
StatusName = status;
}
public static readonly Status Open = new Status("Open");
public static readonly Status Closed = new Status("Closed");
}
Now you need to replace the original Status created by EF with your own:
public Context() : base() {
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
objectContext.ObjectMaterialized += OnObjectMaterialized;
}
private void OnObjectMaterialized(object sender, ObjectMaterializedEventArgs args) {
var objectContext = (ObjectContext)sender;
var entity = args.Entity as Entity;
if (entity != null) {
switch (entity.Status.StatusName) {
case "Open":
entity.Status = Status.Open;
break;
case "Closed":
entity.Status = Status.Closed;
break;
}
// This is necessary because previous code made your object modified
objectContext.DetectChanges();
var entry = objectContext.ObjectStateManager.GetObjectStateEntry(entity);
entry.AcceptChanges();
}
}
It is ugly hack but if you really need this you will have to do something similar.
I'm posting the exact entity:
public class Person : ContactableEntity
{
public Plan Plan { get; set; }
public int Record { get; set; }
public int PersonTypeValue { get; set; }
}
I'm using the following code to update in a disconected context fashion:
public void Update(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Modified;
this.SaveChanges();
}
This is a method exposed by my dbContext
Called this way:
PersistentManager.Update(PersistentManager.Personas,UpdatedPersona);
The problem is, EF will update any property but the referenced Plan object.
Can someone tell me where is the mistake?
In advance : the entity reaches the point of update with all the properties correctly set.
EF just fails to update the FK in the Database (no exception though)
Update:
tried solving the problem like this but it didn't work:
PersistentMgr.Contacts.Attach(Obj);
PersistentMgr.Entry(Obj).State = EntityState.Modified;
PersistentMgr.Entry(Obj.Plan).State = EntityState.Modified;
PersistentMgr.SaveChanges();
You need...
this.Entry(person).State = EntityState.Modified;
this.Entry(person.Plan).State = EntityState.Modified;
...because when you set the state of the person to Modified the person gets attached to the context in state Modified but related entities like person.Plan are attached in state Unchanged.
If the relationship between Person and Plan has been changed while the entities were detached it is more difficult (especially, like in your model, when no foreign key is exposed as property ("independent association")) to update the entities correctly. You basically need to load the original object graph from the database, compare it with detached graph if relationships have been changed and merge the changes into the loaded graph. An example is here (see the second code snippet in that answer).
Edit
Example to show that it works (with EF 5.0):
using System.Data;
using System.Data.Entity;
using System.Linq;
namespace EFModifyTest
{
public class Person
{
public int Id { get; set; }
public Plan Plan { get; set; }
public int Record { get; set; }
public int PersonTypeValue { get; set; }
}
public class Plan
{
public int Id { get; set; }
public string SomeText { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Person> Contacts { get; set; }
public DbSet<Plan> Plans { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
// Create a person with plan
using (var ctx = new MyContext())
{
ctx.Database.Initialize(true);
var plan = new Plan { SomeText = "Old Text" };
var person = new Person { Plan = plan, Record = 1, PersonTypeValue = 11 };
ctx.Contacts.Add(person);
ctx.SaveChanges();
}
// see screenshot 1 from SQL Server Management Studio
Person detachedPerson = null;
// Load the person with plan
using (var ctx = new MyContext())
{
detachedPerson = ctx.Contacts.Include(c => c.Plan).First();
}
// Modify person and plan while they are detached
detachedPerson.Record = 2;
detachedPerson.PersonTypeValue = 12;
detachedPerson.Plan.SomeText = "New Text";
// Attach person and plan to new context and set their states to Modified
using (var ctx = new MyContext())
{
ctx.Entry(detachedPerson).State = EntityState.Modified;
ctx.Entry(detachedPerson.Plan).State = EntityState.Modified;
ctx.SaveChanges();
}
// see screenshot 2 from SQL Server Management Studio
}
}
}
Screenshot 1 from SQL Server Management Studio (before the modification, Person table is left, Plan table is right):
Screenshot 2 from SQL Server Management Studio (after the modification, Person table is left, Plan table is right):
If it doesn't work for you there must be an important difference to my test model and code. I don't know which one, you must provide more details.
Edit 2
If you change the relationship from Person to another (existing) Plan you must load the original and then update the relationship. With independent associations (no FK property in model) you can update relationships only by using change tracking (aside from more advanced modifications of relationship entries in the ObjectContext change tracker):
var originalPerson = this.Contacts.Include(c => c.Plan)
.Single(c => c.Id == person.Id);
this.Plans.Attach(person.Plan);
this.Entry(originalPerson).CurrentValues.SetValues(person);
originalPerson.Plan = person.Plan;
this.SaveChanges();