I'm learing LINQ-to-SQL right now and i have wrote a simple application that define SQL data:
[Table( Name = "items" )]
public class Item
{
[ Column( IsPrimaryKey = true, IsDbGenerated = true ) ]
public int Id;
[ Column ]
public string Name;
}
I have launched 2 copy of application connected to the same .sdf file and tested if all database modifications in one application affects another application. But strange thing arise. If i use InsertOnSubmit() and DeleteOnSubmit() in one application, added/removed items are instantly visible in other application via 'select' LINQ queue. But if i try to modify 'Name' field in one application, it is NOT visible in other applicaton until it reconnects the database :(. The test code i use:
var Items = from c in db.Items
where Id == c.Id
select c;
foreach( var Item in Items )
{
Item.Name = "new name";
break;
}
db.SubmitChanges();
Can anyone suggest what i'm doing wrong and why InsertOnSubmit()/DeleteOnSubmit works and SubmitChanges() don't?
This is related to how Linq-to-Sql manages Object Identity. If you query for the same entity more than once, you will always receive the same object representing the row in the database.
When you insert or delete entities, you are able to see the changes from another client because Linq-to-Sql will need to create/delete the objects associated with those entities and, if there are no conflicts, this presents no problem.
Updating has a different behavior that's explained in the Object Identity article.
LINQ to SQL uses this approach to
manage the integrity of local objects
in order to support optimistic
updates. Because the only changes that
occur after the object is at first
created are those made by the
application, the intent of the
application is clear.
If you need the most updated data from the database, use the DataContext.Refresh with RefreshMode.OverwriteCurrentValues.
Related
I have been struggling with an issue for a couple of days. I am sharing a Content Provider with two different apps (app A and app B). All the stuff regarding DB creation and Content Provider management is done by app A. App B just accesses it using the corresponding Authorities and a Content Provider Client.
ContentProviderClient myCPClient = this.miContext.getContentResolver().acquireContentProviderClient(this.miUri);
The problem comes up when trying to query the database in a more complex way, i.e. using some key words like GROUP BY, HAVING, etc. I need to get unique references according to one specific column (I want to use GROUP BY), and I have found out that there is no rawQuery() method for a ContentProviderClient, but a simplified query() method (compared to the one available in the class SQLiteDatabase, which allows to formulate proper MySQL commands).
I have checked this answer, but since my ContentProvider is accessed from a different app, I do not have any class like MyContentProvider.
To sum up, is there any way to make a proper query (like rawQuery()) to a ContentProvider which was generated by a different app?
I have finally got to a solution which is rather simple and sensible. I got a very good explanation about Content Providers and Content Resolvers. The latter is used to access the former, which means that they can not control what is in the provider, but get data from them. This means that you can not make a Content Provider Client to use a rawQuery() if it is not implemented (override) in the query() method of the corresponding ContentProvider.
To work around my problem, I have used a flag in my provider client and modify my content provider to read it so I can make use of GROUP BY. I just wanted to get unique references from the database according to a particular column.
Here it is the solution, which is not a very clean one, but it works quite well.
For the ContentProviderClient,
ContentProviderClient myCPClient = this.miContext.getContentResolver().acquireContentProviderClient(this.miUri);
//I declare some variables for the query
//'selection' will get all the rows whose "_id" is greater than 0, i.e. all the rows
String selection = BaseDatosParam.Tabla._ID + ">?";
String[] selectionArgs = {"0"};
//'groupBy' is not formatted in any particular way. I just need it to contain the pattern "GROUP BY"
String groupBy = "GROUP BY" + BaseDatosParam.Tabla.REF;
//the last field of the query corresponds to 'sortOrder', but I
Cursor c = myCPClient.query(Uri.parse(miUri.toString()),
projection, selection, selectionArgs, groupBy);
In the ContentProvider,
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
String where = selection;
String groupBy = null;
SQLiteDatabase db = this.miBDManager.getWritableDatabase();
//We just check out whether 'sortOrder' includes the pattern "GROUP BY", otherwise that field will remain null
Pattern myPat = Pattern.compile("GROUP BY");
Matcher myMat = myPat.matcher(sortOrder);
if (myMat.find())
groupBy = myMat.replaceFirst("");
Cursor c = db.query(BaseDatosParam.Tabla.NOMBRE_TABLA, projection, where, selectionArgs, groupBy, null, null);
c.setNotificationUri(this.getContext().getContentResolver(), uri);
return c;
}
Regards,
I'm a bit confused in regards to how EF's dbContext works.
If I do something like _context.Persons.Add(_person) (assuming person is a valid entity), if I then (before calling _context.SaveChanges()) query Persons, will the person I just added be included in the results?
For example:
Person _person = new Person() {Firstname = "Bill", Lastname = "Snerdly"};
_context.Persons.Add(_person);
var _personList = _context.Persons.Where(p => p.Lastname.StartsWith("Sne"));
Whenever I try this, it seems as though the context loses track of the fact that I've added this new person to the context.
What confuses me is that if I edit an existing person and attach the person and set the state to modified, querying the context seems to keep track of the changes that were made and returns them in the results. For example:
//Assuming that Person 5 exists with the name William Snerdly
Person _person = new Person() {Id = 5, Firstname = "Bill", Lastname = "Snerdly"};
_context.Persons.Attach(_person);
_context.Entry(_person).State = System.Data.EntityState.Modified;
var _personList = _context.Persons.Where(p => p.Lastname.StartsWith("Sne"));
In this case, it seems like the person with the id of 5 will show up in the list with the name Bill instead of William. IOW, the context queried the data but retained the changes while in the first scenario, the context queried the data but ignored any added items. It just seems a bit inconsistant.
Am I understanding this correctly or am I missing something?
Thanks for your help with this.
No, as it does not yet exist in the database. It will, however, be accessible through the ObjectStateManager of the ObjectContext, or alternatively, if you're using the DbContext/DbSet wrappers, through the .Local property of the DbSet.
In the case of the edit, you're seeing the ORM's first level cache at work. The query is executed against the database (and so compares against the values in there - your example would get even weirder if you modified the Lastname in the context, but still get the result from the query looking for the unmodified Lastname), but when its results are processed, first the ID of the returned entity is checked, and since the entity with that ID is already present in the context, you get that instance back. This is the default "AppendOnly" mode of operation.
I don't know what you want to do, but I had to understand all that when I wanted to validate my changes according to rules that needed to use the values of both loaded and unread entities. I ended up starting a transaction, saving the changes with the "None" options, doing my validation queries againt the database (which then contained the "merged" view of the data), and the rolling back the transaction if the data was invalid, or accepting the changes and committing the transaction otherwise.
I am new to LINQ to SQL, but have done a lot of database development in the past.
The software I just started working on uses:
// MyDataContext is a sub class of DataContext, that is generated with SqlMetal
MyDataContext db = new MyDataContext (connectionString);
db.CreateDatabase();
to create the database when it is first run.
I need to add some indexes to the tables....
How can I tell the DataContext what indexes I want?
Otherwise how do I control this?
(I could use a sql script, but I like the ideal that db.CreateDatabase will always create a database that matches the data access code)
(For better, or worse the software has full access to the database server and our software often create databases on the fly to store result of model runs etc, so please don’t tell me we should not be creating databases from code)
I seem not to be the only person hitting limts on DataContext.CreateDatabase() see also http://csainty.blogspot.com/2008/02/linq-to-sql-be-careful-of.html
As far as I know the DataContext.CreateDatabase method can only create primary keys.
When you look at the DBML directly, you will see that there are no elements for defining an index. Therefore it is, IMHO, save to assume that CreateDatabase cannot do it.
So the only way I can think of for creating indexes "automatically" is by first calling DataContext.CreateDatabase and then calling DataContext.ExecuteCommand to add the indexes to the tables that were just created.
You can execute SQL Command on DatabaseCreated method.
public partial class DatabaseModelsDataContext : System.Data.Linq.DataContext
{
partial void OnCreated ()
{
var cmdText = #"
IF EXISTS (SELECT name FROM sys.indexes WHERE name = N'IX_LeafKey')
DROP INDEX IX_MyTableColumn
ON [mydb].[dbo].[Leaf];
CREATE INDEX IX_MyTableColumn
ON [mydb].[dbo].[MyTable] ([column]) ;";
ExecuteCommand(cmdText);
}
}
Consider the following code block:
using (PlayersDataContext context = new PlayersDataContext())
{
Console.WriteLine(context.Players.Count()); // will output 'x'
context.Players.InsertOnSubmit(new Player {FirstName = "Vince", LastName = "Young"});
Console.WriteLine(context.Players.Count()); // will also output 'x'; but I'd like to output 'x' + 1
}
Given that I haven't called
context.SubmitChanges();
the application will output the same player count both before and after the InsertOnSubmit statement.
My two questions:
Can the DataContext instance return collections that include pending changes?
Or must I reconcile the DataContext instance with context.GetChangeSet()?
Sure, use:
context.GetChangeSet()
and for more granularity, there are members for Inserts, Updates, and Deletes.
EDIT: I understand your new question now. Yes, if you wanted to include changes in the collection, you would have to somehow combine the collections returned by GetChangeSet() and your existing collections.
I ran into an interesting problem while using DLINQ. When I instantiate an entity, calling .SubmitChanges() on the DataContext will insert a new row into the database - without having ever called .Insert[All]OnSubmit(...).
//Code sample:
Data.NetServices _netServices = new Data.NetServices(_connString);
Data.ProductOption[] test = new Data.ProductOption[]
{
new Data.ProductOption
{
Name="TEST1",
//Notice the assignment here
ProductOptionCategory=_netServices.ProductOptionCategory.First(poc => poc.Name == "laminate")
}
};
_netServices.SubmitChanges();
Running the code above will insert a new row in the database. I noticed this effect while writing an app to parse an XML file and populate some tables. I noticed there were 1000+ inserts when I was only expecting around 50 or so - then I finally isolated this behavior.
How can I prevent these objects from being persisted implicitly?
Thanks,
-Charles
Think of the relationship as having two sides. When you set one side of the relationship the other side needs to be updated so in the case above as well as setting the ProductOptionCategory it is effectively adding the new object to the ProductOptions relationship on the laminate ProductOptionCategory side.
The work-around is as you have already discovered and to set the underlying foreign key instead so LINQ to SQL will not track the objects in the usual way and require implicit indication it should persist the object.
Of course the best solution for performance would be to determine from the source data which objects you don't want to add and never create the instance in the first place.