I'm very new to this Entity Framework Object Services Overview (Entity Framework), so forgive me if I use the wrong terminology here.
I'm using the EDMX file to connect to an SQLite database. What I'm trying to do is use the ObjectSet<T> normally, to access a collection of objects from a table in the database. However, I want to additionally store some run-time-only data in the objects in that set. In my case, I have a set of devices stored in the database, but upon startup, I want to mark them as "Connected" or "Disconnected", and keep track of this state throughout execution.
Since the (row) types generated by the EDMX are partial I've added another partial definition, and added my public bool Connected property there. This seems to work, I can set it, and future queries provide objects with the same value that I previously set. The problem is, I don't know a) how it is working, or b) whether I can trust it. These doubts come from the fact that these aren't really collections of objects we're dealing with, right?
Hopefully that made sense, else I can provide more detail.
What you're doing is completely safe.
ObjectSet is still a collection of objects. With a lot magic added underneath.
I am not an expert on the internals but here is how I think it works:
The Entity Framework has a StateTracker hat keeps track of all the entities you're working with.
Every class in your EDMX model is required to have a key. EF is using that key internally so that it loads that specific object only once into memory.
var foo = db.Foos.Single(x => x.Id == 1); // foo with Id 1 is unique (in memory)
var foo2 = db.Foos.Single(x => x.Id == 1); // same instance of foo, but with updated values
var foo3 = db.Foos.Single(x => x.Id == 2) // a new unique instance (Id = 2)
bool sameObject = Object.Equals(foo, foo2); // will return true;
At every select the following happens:
Is an instance of class Foo already tracked/does it already exist?
Yes -> update the properties of the existing instance from the database.
No -> create new instance of class Foo (take values from database)
Of course it can only ever update mapped properties. So the ones you defined in the partial class won't be overwritten.
In case you're going to use code first. There is also the [NotMapped] attribute, that makes sure that the property won't be included in the table if you generate a new database from your code first models.
I hope I could clarify some things for you.
Related
I have quite some entities in the DB and all of them have a column decimal DBSTATE indicating if the entry is active (1) or not (0). To make a quick work of getting the instances, I created the following generic function that returns only the active columns of entities:
IEnumerable<DBType> GetActiveEntries<DBType>()
where DBType : class, IDBEntry
{
return db.Set<DBType>().Where(e => e.DBStateInDB == (decimal)DBState.Active).AsEnumerable();
}
IDBEntry is an interface that all the model classes implement by returning its DBSTATE value, e.g. this is how REGULARCUSTOMER implements it (irrelevant parts are omitted):
public decimal DBStateInDB => this.DBSTATE
As it turns out, this is not possible, because EF can only work with its own types in queries, this is why the following non-generic function works:
IEnumerable<REGULARCUSTOMER> GetActives_TEMP()
{
return db.REGULARCUSTOMERs.Where(e => e.DBSTATE == (decimal)DBState.Active).AsEnumerable();
}
So my question: is it possible to somehow avoid writing separate functions/switch-cases for all the entities or I really stuck with that option?
I'd say you have to go around the problem.
One possible way of doing it would be to add Navigational properties (by creating a new entity type) to your entities, as DbSet<EntityState> (for example) which your entity would have a ForeignKey defining the State, this would oblige you to have a supplementary table which would contain the ID's of every entity and a bool for their state, making it clean and accessible. This also means that with your newly created Navigational property you could access it with YOUR_ENTITY.EntityState.State or YOUR_ENTITY.EntityState.Id, it would also give you the capacity to modify/access a entities State regardless of their type because of the new table. Your type enherited rules will still apply and changing the table will change the entity, regardless of Type.
But again, this is only a way of doing. It should be relatively simple to implement and can be quite powerful.
Let me know what you think.
My model looks something like this:
Company
-Locations
Locations
-Stores
Stores
-Products
So I want to make a copy of a Company, and all of its associations should also be copied and saved to the database.
How can I do this if I have the Company loaded in memory?
Company company = DbContext.Companies.Find(123);
If it is tricky, I can loop through each association and then call create a new object. The Id's will be different but everything else should be the same.
I am using EF 6.
Cloning object graphs with EF is a piece of cake:
var company = DbContext.Companies.AsNoTracking()
.Include(c => c.Locations
.Select(l => l.Stores
.Select(s => s.Products)))
.Where(c => c.Id == 123)
.FirstOrDefault();
DbContext.Companies.Add(company);
DbContext.SaveChanges();
A few things to note here.
AsNoTracking() is vital, because the objects you add to the context shouldn't be tracked already.
Now if you Add() the company, all entities in its object graph will be marked as Added as well.
I assume that the database generates new primary key values (identity columns). If so, EF will ignore the current values from the existing objects in the database. If not, you'll have to traverse the object graph and assign new values yourself.
One caveat: this only works well if the associations are 1:0..n. If there is a n:m association, identical entities may get inserted multiple times. If, for example, Store-Product is n:m and product A occurs at store 1 and store 2, product A will be inserted twice. If you want to prevent this, you should fetch the objects by one context, with tracking (i.e. without AsNoTracking), and Add() them in a new context. By enabling tracking, EF keeps track of identical entities and won't duplicate them. In this case, proxy creation should be disabled, otherwise the entities keep a reference to the context they came from.
More details here: Merge identical databases into one
I would add a method to each model that needs to be cloneable this way, I'd recommend an interface for it also.
It could be done something like this:
//Company.cs
Company DeepClone()
{
Company clone = new Company();
clone.Name = this.name;
//...more properties (be careful when copying reference types)
clone.Locations = new List<Location>(this.Locations.Select(l => l.DeepClone()));
return clone;
}
You should repeat this basic pattern for every class and "child" class that needs to be copiable. This way each object is aware of how to create a deep clone of its self, and passes responsibility for child objects off to the child class, neatly encapsulating everything.
It could be used this way:
Company copyOfCompany123 = DbContext.Companies.Find(123).DeepClone;
My apologies if there are any errors in the above code; I don't have Visual Studio available at the moment to verify everything, I'm working from memory.
One other really simple and code efficient way to deeply clone an object using serialization can be found in this post How do you do a deep copy an object in .Net (C# specifically)?
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}
Just be aware that this can have some pretty serious resource and performance issues depending on your object structure. Every class that you want to use it on must also be marked with the [Serializable] attribute.
As I've mentioned in a couple other questions, I'm currently trying to replace a home-grown ORM with the Entity Framework, now that our database can support it.
Currently, we have certain objects set up such that they are mapped to a table in our internal database and a table in the database that runs our website (which is not even in the same state, let alone on the same server). So, for example:
Part p = new Part(12345);
p.Name = "Renamed part";
p.Update();
will update both the internal and the web databases simultaneously to reflect that the part with ID 12345 is now named "Renamed part". This logic only needs to go one direction (internal -> web) for the time being. We access the web database through a LINQ-to-SQL DBML and its objects.
I think my question has two parts, although it's possible I'm not asking the right question in the first place.
Is there any kind of "OnUpdate()" event/method that I can use to trigger validation of "Should this be pushed to the web?" and then do the pushing? If there isn't anything by default, is there any other way I can insert logic between .SaveChanges() and when it hits the database?
Is there any way that I can specify for each object which DBML object it maps to, and for each EF auto-generated property which property on the L2S object to map to? The names often match up, but not always so I can't rely on that. Alternatively, can I modify the L2S objects in a generic way so that they can populate themselves from the EF object?
Sounds like a job for Sql Server replication.
You don't need to inter-connect the two together as it seems you're saying with question 2.
Just have the two separate databases with their own EF or L2S models and abstract them away using repositories with domain objects.
This is the solution I ended up going with. Note that the implementation of IAdvantageWebTable is inherited from the existing base class, so nothing special needed to be done for EF-based classes, once the T4 template was modified to inherit correctly.
public partial class EntityContext
{
public override int SaveChanges(System.Data.Objects.SaveOptions options)
{
var modified = this.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added); // Get the list of things to update
var result = base.SaveChanges(options); // Call the base SaveChanges, which clears that list.
using (var context = new WebDataContext()) // This is the second database context.
{
foreach (var obj in modified)
{
var table = obj.Entity as IAdvantageWebTable;
if (table != null)
{
table.UpdateWeb(context); // This is IAdvantageWebTable.UpdateWeb(), which calls all the existing logic I've had in place for years.
}
}
context.SubmitChanges();
}
return result;
}
}
I am attempting to add some multi-threading to a WPF application I have created in order to create a more responsive interface, but as Linq-to-SQL data contexts are not thread safe, I am forced to use one per thread.
My problem is that the same entity pulled from two different contexts, are apparently not equal. Take the following code sample, where I have a simple database with employee records:
var context1 = new DataModelDataContext();
var context2 = new DataModelDataContext();
var emp1 = context1.Employees.Single(x => x.ID == 1);
var emp2 = context2.Employees.Single(x => x.ID == 1);
Console.WriteLine(string.Format("Employees equal: {0}", emp1 == emp2));
Console.ReadKey();
When run, this returns:
Employees equal: False
In my mind I would expect these to objects to be equal, as they would be if I pulled them from the same context. I can overcome this by checking emp1.ID == emp2.ID instead, but this is problematic when trying to use WPF bindings such as SelectedItem.
Is there any way around this? This behaviour appears to be the same in Entity Framework as well.
Two independently instantiated objects may represent the same item in the data store, but they will never evaluate as equal in any language. You will have to write code that compares the members of each object to determine if their data is equal. This may or may not be as simple as comparing the primary key.
You can always override Equals and GetHasCode to ensure that objects are "equal" even if they are not same instance (which is default equality rule used for reference types).
As #cdonner stated, when you load an object from the data store - two different instances of the object will be created with identical data. This will mean that object1 != object2.
One way of overcoming this is to set up a cache like dictionary in your repository. Example: Dictionary<Type, object>. Where object is the type of your indentifier (int in this case).
So rather than querying the data store by using inline code like context1.Employees.Single(x => x.ID == 1); you could set it up so that you call it like Repositories.Employees.WithID(1);
What this would then do is check the local repository cache for an Employee object with ID == 1, if this is available return it instead of querying the data store.
From then on, your references will always be the same.
This may need some refining for when you want to update and\or refresh the object in memory from the data store so that you don't persist stale data, and so that when you refresh the data from the data store, you update your cache.
I have a Linq object, and I want to make changes to it and save it, like so:
public void DoSomething(MyClass obj) {
obj.MyProperty = "Changed!";
MyDataContext dc = new MyDataContext();
dc.GetTable<MyClass>().Attach(dc, true); // throws exception
dc.SubmitChanges();
}
The exception is:
System.InvalidOperationException: An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.
It looks like I have a few choices:
put a version member on every one of my Linq classes & tables (100+) that I need to use in this way.
find the data context that originally created the object and use that to submit changes.
implement OnLoaded in every class and save a copy of this object that I can pass to Attach() as the baseline object.
To hell with concurrency checking; load the DB version just before attaching and use that as the baseline object (NOT!!!)
Option (2) seems the most elegant method, particularly if I can find a way of storing a reference to the data context when the object is created. But - how?
Any other ideas?
EDIT
I tried to follow Jason Punyon's advice and create a concurrency field on on table as a test case. I set all the right properties (Time Stamp = true etc.) on the field in the dbml file, and I now have a concurrency field... and a different error:
System.NotSupportedException: An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.
So what the heck am I supposed to attach, then, if not an existing entity? If I wanted a new record, I would do an InsertOnSubmit()! So how are you supposed to use Attach()?
Edit - FULL DISCLOSURE
OK, I can see it's time for full disclosure of why all the standard patterns aren't working for me.
I have been trying to be clever and make my interfaces much cleaner by hiding the DataContext from the "consumer" developers. This I have done by creating a base class
public class LinqedTable<T> where T : LinqedTable<T> {
...
}
... and every single one of my tables has the "other half" of its generated version declared like so:
public partial class MyClass : LinqedTable<MyClass> {
}
Now LinqedTable has a bunch of utility methods, most particularly things like:
public static T Get(long ID) {
// code to load the record with the given ID
// so you can write things like:
// MyClass obj = MyClass.Get(myID);
// instead of:
// MyClass obj = myDataContext.GetTable<MyClass>().Where(o => o.ID == myID).SingleOrDefault();
}
public static Table<T> GetTable() {
// so you can write queries like:
// var q = MyClass.GetTable();
// instead of:
// var q = myDataContext.GetTable<MyClass>();
}
Of course, as you can imagine, this means that LinqedTable must somehow be able to have access to a DataContext. Up until recently I was achieving this by caching the DataContext in a static context. Yes, "up until recently", because that "recently" is when I discovered that you're not really supposed to hang on to a DataContext for longer than a unit of work, otherwise all sorts of gremlins start coming out of the woodwork. Lesson learned.
So now I know that I can't hang on to that data context for too long... which is why I started experimenting with creating a DataContext on demand, cached only on the current LinqedTable instance. This then led to the problem where the newly created DataContext wants nothing to do with my object, because it "knows" that it's being unfaithful to the DataContext that created it.
Is there any way of pushing the DataContext info onto the LinqedTable at the time of creation or loading?
This really is a poser. I definitely do not want to compromise on all these convenience functions I've put into the LinqedTable base class, and I need to be able to let go of the DataContext when necessary and hang on to it while it's still needed.
Any other ideas?
Updating with LINQ to SQL is, um, interesting.
If the data context is gone (which in most situations, it should be), then you will need to get a new data context, and run a query to retrieve the object you want to update. It's an absolute rule in LINQ to SQL that you must retrieve an object to delete it, and it's just about as iron-clad that you should retrieve an object to update it as well. There are workarounds, but they are ugly and generally have lots more ways to get you in trouble. So just go get the record again and be done with it.
Once you have the re-fetched object, then update it with the content of your existing object that has the changes. Then do a SubmitChanges() on the new data context. That's it! LINQ to SQL will generate a fairly heavy-handed version of optimistic concurrency by comparing every value in the record to the original (in the re-fetched) record. If any value changed while you had the data, LINQ to SQL will throw a concurrency exception. (So you don't need to go altering all your tables for versioning or timestamps.)
If you have any questions about the generated update statements, you'll have to break out SQL Profiler and watch the updates go to the database. Which is actually a good idea, until you get confidence in the generated SQL.
One last note on transactions - the data context will generate a transaction for each SubmitChanges() call, if there is no ambient transaction. If you have several items to update and want to run them as one transaction, make sure you use the same data context for all of them, and wait to call SubmitChanges() until you've updated all the object contents.
If that approach to transactions isn't feasible, then look up the TransactionScope object. It will be your friend.
I think 2 is not the best option. It's sounding like you're going to create a single DataContext and keep it alive for the entire lifetime of your program which is a bad idea. DataContexts are lightweight objects meant to be spun up when you need them. Trying to keep the references around is also probably going to tightly couple areas of your program you'd rather keep separate.
Running a hundred ALTER TABLE statements one time, regenerating the context and keeping the architecture simple and decoupled is the elegant answer...
find the data context that originally created the object and use that to submit changes
Where did your datacontext go? Why is it so hard to find? You're only using one at any given time right?
So what the heck am I supposed to attach, then, if not an existing entity? If I wanted a new record, I would do an InsertOnSubmit()! So how are you supposed to use Attach()?
You're supposed to attach an instance that represents an existing record... but was not loaded by another datacontext - can't have two contexts tracking record state on the same instance. If you produce a new instance (ie. clone) you'll be good to go.
You might want to check out this article and its concurrency patterns for update and delete section.
The "An entity can only be attached as modified without original state if it declares a version member" error when attaching an entitity that has a timestamp member will (should) only occur if the entity has not travelled 'over the wire' (read: been serialized and deserialized again). If you're testing with a local test app that is not using WCF or something else that will result in the entities being serialized and deserialized then they will still keep references to the original datacontext through entitysets/entityrefs (associations/nav. properties).
If this is the case, you can work around it by serializing and deserializing it locally before calling the datacontext's .Attach method. E.g.:
internal static T CloneEntity<T>(T originalEntity)
{
Type entityType = typeof(T);
DataContractSerializer ser =
new DataContractSerializer(entityType);
using (MemoryStream ms = new MemoryStream())
{
ser.WriteObject(ms, originalEntity);
ms.Position = 0;
return (T)ser.ReadObject(ms);
}
}
Alternatively you can detach it by setting all entitysets/entityrefs to null, but that is more error prone so although a bit more expensive I just use the DataContractSerializer method above whenever I want to simulate n-tier behavior locally...
(related thread: http://social.msdn.microsoft.com/Forums/en-US/linqtosql/thread/eeeee9ae-fafb-4627-aa2e-e30570f637ba )
You can reattach to a new DataContext. The only thing that prevents you from doing so under normal circumstances is the property changed event registrations that occur within the EntitySet<T> and EntityRef<T> classes. To allow the entity to be transferred between contexts, you first have to detach the entity from the DataContext, by removing these event registrations, and then later on reattach to the new context by using the DataContext.Attach() method.
Here's a good example.
When you retrieve the data in the first place, turn off object tracking on the context that does the retrieval. This will prevent the object state from being tracked on the original context. Then, when it's time to save the values, attach to the new context, refresh to set the original values on the object from the database, and then submit changes. The following worked for me when I tested it.
MyClass obj = null;
using (DataContext context = new DataContext())
{
context.ObjectTrackingEnabled = false;
obj = (from p in context.MyClasses
where p.ID == someId
select p).FirstOrDefault();
}
obj.Name += "test";
using (DataContext context2 = new ())
{
context2.MyClasses.Attach(obj);
context2.Refresh(System.Data.Linq.RefreshMode.KeepCurrentValues, obj);
context2.SubmitChanges();
}