I'm doing a data migration of a small set of tables. The keys are of course alinged in the source database and I have composed initialization statements similar to this one:
context.Countries.Add(new Country{CountryId=75,CountryName="US"});
context.Countries.Add(new Country{CountryId=89,CountryName="Argentina"});
However, when I look in the database afterwards I find this:
CountryID | CountryName
------------------------
1 | US
2 | Argentina
(It disregards the CountryIds I provide and uses n+1 values for this identity column.)
In an effort to solve the problem I tried this:
context.Database.ExecuteSqlCommand("Set IDENTITY_INSERT COUNTRIES ON");
GetCountries().ForEach(c=>context.Countries.Add(c));
context.Database.ExecuteSqlCommand("Set IDENTITY_INSERT COUNTRIES OFF");
But I see no difference in the keys in the db.
How do I do an Identity Insert from my Initializer's Seed() method?
EDIT:
I'm posting the results of my final attempt for the benefit of anyone else following after:
private void InsertCountries(SodContext context)
{
context.Database.ExecuteSqlCommand("Set IDENTITY_INSERT COUNTRIES ON");
string cmd = "INSERT INTO COUNTRIES (CountryId,Name,IsActive) values ({0},'{1}',{2})";
GetCountries().ForEach(cntry=>context.Database.ExecuteSqlCommand(string.Format(cmd,cntry.CountryId,cntry.Name,cntry.IsActive==false?0:1)));
context.Database.ExecuteSqlCommand("Set IDENTITY_INSERT COUNTRIES OFF");
}
However, I get the error message that I have to turn IDENTITY_INSERT ON.
I'm giving up and switching over to doing this in a TSQL script.
If you have configured your primary key properties as database generated identities(ie: Auto increment) then EF will not include that column in the insert statements that are issued. So EF will request the generated key and update the primary key property.
What you can do is script the necessary data and execute that script directly without using entities.
I had a go at what you were trying in the c# code.
Putting everything in the same sql command statement does work.
const string sqlCmd = "set identity_insert [role] on insert into [role](id, name) values({0}, {1}) set identity_insert [role] off";
context.Database.ExecuteSqlCommand(sqlCmd,(int)SystemRole.SuperAdministrator, superAdminRole);
context.Database.ExecuteSqlCommand(sqlCmd, (int)SystemRole.Administrator, adminRole);
context.Database.ExecuteSqlCommand(sqlCmd, (int)SystemRole.Manager, managerRole);
context.Database.ExecuteSqlCommand(sqlCmd, (int)SystemRole.User, userRole);
Related
I'm trying to change my code first ID column from 'int' to 'Guid', and when trying to run the migration, I get the message:
Identity column 'CustomFieldId' must be of data type int, bigint, smallint, tinyint, or decimal or numeric with a scale of 0, and constrained to be nonnullable.
I'm defining the column like this:
public partial class CustomField : BaseEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid CustomFieldId { get; set; }
Mapping it in CustomFieldMapping.cs like this:
public CustomFieldMapping()
{
//Primary key
HasKey(t => t.CustomFieldId);
//Constraints
Property(t => t.CustomFieldId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
And the migration that's generated is trying to do this:
public override void Up()
{
DropForeignKey("dbo.CustomField", "CustomFormId", "dbo.CustomForm");
DropForeignKey("dbo.CustomData", "CustomFieldId", "dbo.CustomField");
DropForeignKey("dbo.CustomForm", "ParentFormId", "dbo.CustomForm");
DropIndex("dbo.CustomField", new[] { "CustomFormId" });
DropIndex("dbo.CustomForm", new[] { "ParentFormId" });
DropIndex("dbo.CustomData", new[] { "CustomFieldId" });
DropPrimaryKey("dbo.CustomField");
DropPrimaryKey("dbo.CustomForm");
AlterColumn("dbo.CustomField", "CustomFieldId", c => c.Guid(nullable: false));
AlterColumn("dbo.CustomField", "SortOrder", c => c.Int(nullable: false));
AlterColumn("dbo.CustomForm", "CustomFormId", c => c.Guid(nullable: false));
AlterColumn("dbo.CustomForm", "ParentFormId", c => c.Guid());
AddPrimaryKey("dbo.CustomField", "CustomFieldId");
AddPrimaryKey("dbo.CustomForm", "CustomFormId");
CreateIndex("dbo.CustomField", "CustomForm_CustomFormId");
CreateIndex("dbo.CustomForm", "ParentFormId");
CreateIndex("dbo.CustomData", "CustomField_CustomFieldId");
AddForeignKey("dbo.CustomField", "CustomForm_CustomFormId", "dbo.CustomForm", "CustomFormId");
AddForeignKey("dbo.CustomData", "CustomField_CustomFieldId", "dbo.CustomField", "CustomFieldId");
AddForeignKey("dbo.CustomForm", "ParentFormId", "dbo.CustomForm", "CustomFormId");
I would like it to be a sequentially incremented Guid. What am I doing wrong?
To solve this problem I used a Sql() method in the Up() and Down() methods of the migration class. The SQL command string in the Up() method removes the primary key constraint on the ID column, drops the ID column of type int and then adds a new ID column with of type Guid. The Down() method does the same thing but drops the Guid column and adds a new int column.
I found a few solutions on Stack Overflow that resolve the "change column type" by running a SQL command in a query window. To address your comment:
We're just trying to keep a clean/clear migration path to trace when
we did what which is not always easy with SQL.
I used SQL commands within the Up() and Down() migration methods. For me this solution works well in my projects.
The solution at the bottom of this answer was constructed from several Stack Overflow questions/answers. Skip to that for just the code. Here are the long-winded details.
Using SQL commands in a migration class
I couldn't find a solution that used Entity Framework migration methods like AlterColumn() and DropColumn() ONLY.
Rather than using a mix of migration methods and commands in the Sql() method, I used all SQL commands within a string in the Sql() migration method. Using all SQL commands made it easier to test in a query window in Visual Studio or SQL Server Management Studio.
The answer by 'Uchitha' gave the starting steps for adding the Sql() "method within the desired migration class."
Generate migration class using Add-Migration
Alter the class using code similar to above
Run the migration using Update-Database
The Sql() method sample in the answer looks like:
Sql("UPDATE dbo.YourTable SET Column1 = 'VALUE1' ");
Changing the column type - generic steps
I used the answer by 'JustAnotherUserYouMayKnow' to get started on the steps to change the column type. I didn't follow this explicitly but it provided just the basic framework of the need to drop a column and recreating it.
Add a new column with your new type
Use Sql() to take over the data from the original column using an update statement
Remove the old column
Rename the new column
Sequential GUIDs
The answer from 'Icarus' provided the ALTER TABLE statement with the use of newsequentialid() to generate sequential GUIDs as per your statement:
I would like it to be a sequentially incremented Guid.
ALTER TABLE your_table
ADD your_column UNIQUEIDENTIFIER DEFAULT newsequentialid() NOT null
Take note of privacy concerns by 'Johan' in the comment section of the answer by 'Icarus':
If privacy is a concern, do not use newsequentialid(). It is possible to guess the value of the next generated GUID and, therefore, access data associated with that GUID
Alter primary key
The column you want to change is an ID column and you've set it as the primary key. Therefore, before dropping the existing ID column you'll need to remove the primary key using another ALTER TABLE SQL command.
See the selected answer from 'darnir' for "How can I alter a primary key constraint using SQL syntax?"
ALTER TABLE <Table_Name>
DROP CONSTRAINT <constraint_name>
ALTER TABLE <Table_Name>
ADD CONSTRAINT <constraint_name> PRIMARY KEY (<Column1>,<Column2>)
See the note by 'Oleg' to determine if this will be a factor:
PRIMARY KEY CONSTRAINT cannot be altered, you may only drop it and create again. For big datasets it can cause a long run time and thus - table inavailability.
I had problems when the command with DROP CONSTRAINT above was executed. The results pane listed a constraint that was auto-generated even though I'd used a specific constraint name in the ALTER TABLE ... ADD COLUMN command. See this question "Why does SQL keep creating a DF constraint?" and this question if you experience something similar.
To fix the problem with dropping the constraint I used the answer by 'ScubaSteve' from this question: "How to drop SQL default constraint without knowing its name?" With the addition of the note by 'Seven' here are the SQL commands:
DECLARE #ObjectName NVARCHAR(100)
SELECT #ObjectName = OBJECT_NAME([default_object_id]) FROM SYS.COLUMNS
WHERE [object_id] = OBJECT_ID('[tableSchema].[tableName]') AND [name] = 'columnName';
IF #ObjectName IS NOT NULL EXEC('ALTER TABLE [tableSchema].[tableName] DROP CONSTRAINT ' + #ObjectName)
The comment by 'Seven' in 'ScubaSteve's answer. I added the 'if' condition as at times the EXEC would fail when no constraint was found.
To make this script idempotent add IF #ObjectName IS NOT NULL before EXEC command
The final solution
Make sure to replace MyTableName, MyColumnName, and dbo in the code below to your table name, column name (e.g. set column name to Id) and table schema respectively.
public override void Up()
{
Sql(#"
DECLARE #ObjectName NVARCHAR(100)
SELECT #ObjectName = OBJECT_NAME([default_object_id]) FROM SYS.COLUMNS
WHERE [object_id] = OBJECT_ID('[dbo].[MyTableName]') AND [name] = 'MyColumnName';
IF #ObjectName IS NOT NULL EXEC('ALTER TABLE [dbo].[MyTableName] DROP CONSTRAINT ' + #ObjectName)
ALTER TABLE dbo.MyTableName DROP CONSTRAINT PK_MyTableName, COLUMN MyColumnName
ALTER TABLE dbo.MyTableName
ADD Id UNIQUEIDENTIFIER DEFAULT (newsequentialid()) NOT NULL
CONSTRAINT PK_MyTableName
PRIMARY KEY CLUSTERED ([MyColumnName])
");
}
public override void Down()
{
Sql(#"
DECLARE #ObjectName NVARCHAR(100)
SELECT #ObjectName = OBJECT_NAME([default_object_id]) FROM SYS.COLUMNS
WHERE [object_id] = OBJECT_ID('[dbo].[MyTableName]') AND [name] = 'MyColumnName';
IF #ObjectName IS NOT NULL EXEC('ALTER TABLE [dbo].[MyTableName] DROP CONSTRAINT ' + #ObjectName)
ALTER TABLE dbo.MyTableName DROP CONSTRAINT PK_MyTableName, COLUMN Id
ALTER TABLE MyTableName
ADD MyColumnName int IDENTITY(1, 1) NOT NULL
CONSTRAINT PK_MyTableName
PRIMARY KEY CLUSTERED ([MyColumnName] ASC)
");
}
I found a simple solution for the problem. You just need to drop the column CustomFieldId then add it back as a Guid column. That way there won't be any error message and the migration will pass:
public override void Up()
{
DropForeignKey("dbo.CustomField", "CustomFormId", "dbo.CustomForm");
DropForeignKey("dbo.CustomData", "CustomFieldId", "dbo.CustomField");
DropForeignKey("dbo.CustomForm", "ParentFormId", "dbo.CustomForm");
DropIndex("dbo.CustomField", new[] { "CustomFormId" });
DropIndex("dbo.CustomForm", new[] { "ParentFormId" });
DropIndex("dbo.CustomData", new[] { "CustomFieldId" });
DropPrimaryKey("dbo.CustomField");
DropPrimaryKey("dbo.CustomForm");
DropColumn("dbo.CustomField", "CustomFieldId")
AddColumn("dbo.CustomField", "CustomFieldId", c => c.Guid(nullable: false));
AlterColumn("dbo.CustomField", "SortOrder", c => c.Int(nullable: false));
AlterColumn("dbo.CustomForm", "CustomFormId", c => c.Guid(nullable: false));
AlterColumn("dbo.CustomForm", "ParentFormId", c => c.Guid());
AddPrimaryKey("dbo.CustomField", "CustomFieldId");
AddPrimaryKey("dbo.CustomForm", "CustomFormId");
CreateIndex("dbo.CustomField", "CustomForm_CustomFormId");
CreateIndex("dbo.CustomForm", "ParentFormId");
CreateIndex("dbo.CustomData", "CustomField_CustomFieldId");
AddForeignKey("dbo.CustomField", "CustomForm_CustomFormId", "dbo.CustomForm", "CustomFormId");
AddForeignKey("dbo.CustomData", "CustomField_CustomFieldId", "dbo.CustomField", "CustomFieldId");
AddForeignKey("dbo.CustomForm", "ParentFormId", "dbo.CustomForm", "CustomFormId");
I'm having some difficulties understanding the Concurrency problem using Update store procedures. I'm following Julie Lerman's Programming Entity Framework and she gives the following code in an example:
using (var context = new BAEntities())
{
var payment = context.Payments.First();
if (payment.PaymentDate != null)
{
payment.PaymentDate = payment.PaymentDate.Value.AddDays(1);
}
var origRowVersion = payment.RowVersion;
try
{ //BREAKPOINT #1
context.SaveChanges();
var newRowVersion = payment.RowVersion;
if (newRowVersion == origRowVersion)
{
Console.WriteLine("RowVersion not updated");
}
else
{
Console.WriteLine("RowVersion updated");
}
}
catch (OptimisticConcurrencyException)
{
Console.WriteLine("Concurrency Exception was thrown");
}
}
The Update SP looks like:
UPDATE payments
SET paymentdate=#date,reservationID=#reservationID,amount=#amount, modifieddate=#modifiedDate
WHERE
paymentid=#paymentid AND ROWVERSION=#rowversion
IF ##ROWCOUNT>0
SELECT RowVersion AS newTimeStamp FROM payments WHERE paymentid=#paymentid
and the "Use original value" checkbox is ticked in the mapping, which looks like this:
https://dl.dropboxusercontent.com/u/135754/updatemapping.png
Now, when I try to:
run the code as it is, then the newRowVersion inspected in the debugger is same as origRowversion, but the app enters 'else' clause (why is it the same in the first place, I have just changed it? is it debugger issue?)
run the code, but in the BREAKPOINT #1 I update the payment object in Management Studio, the SaveChanges throws OptimisticConcurrencyException. I assume this is expected result.
Each time when I look in the SQL Profiler, the original version of timestamp is sent to the server.
Then, when I untick the "Use original value" in the SP mappings for the timestamp value, everything works the same way as described above... I don't get the idea of it. Am I testing it wrong? When is the app supposed to enter the 'if' clause?
Thanks in advance, cheers!
EDIT:
I added newTimeStamp as the return value for the Update SP mapping. Now I can see that the updated value of RowVersion is correctly taken from the DB. But I still cannot see the difference between having "Use original value" checked and unchecked...
I think I get it now.
When I try to manually change the rowversion (to a random byte[]) before calling savechanges then:
Use Original Value unchecked: the 'random byte[]' is sent to the DB and used in the update stored procedure (in WHERE clause), causing OptimisticConcurrencyException
Use Original Value checked: the value that rowversion had when it was originally downloaded from DB is sent and used in the update stored procedure (in WHERE clause)
I guess this is what Use Original Value is for... It just seems a little weird to me, who would change it manually in the same dbcontext?
I'm trying to add an object to a database-first ORM EntitySet in an MVC project. I use a piece of code something like this:
public static Boolean CreateListing(string title, string description)
{
ListingEntities ce = new ListingEntities();
ce.Ads.AddObject(new Ad()
{
ID = Guid.NewGuid(),
Title = title,
Description = description,
});
return ce.SaveChanges() == 1;
}
However, the SaveChanges method throws a Data.UpdateException which is thrown by a SqlClient.SqlException. The latter says
"Cannot insert the value NULL into column 'ID', table 'Listings.dbo.Ads'; column does not allow nulls. INSERT fails.
The statement has been terminated."
I wholeheartedly agree. I just don't see why the ID should be null when it seems I set it immediately prior. Any suggestions?
Thanks,
Nathan
Someone else on my team configured the database to create its own ID's, and the issue is resolved.
Today I migrated an old application from EF 4.2 to EF 4.3.1.
In my app I was using CodeFirst, but after migration it stopped working, and couldn't find a reason for that.
To clear any other possible problem I decided to create a small console application and I used the Data Migration walk-through published by the ADO team:
http://blogs.msdn.com/b/adonet/archive/2012/02/09/ef-4-3-code-based-migrations-walkthrough.aspx
I copied exactly the code of the blog, but instead of working correctly (creating the DB, creating the schema, and inserting the blog) I get some errors:
only the DB is created, but no tables
I get this error Conversion failed when converting datetime from character string."
All of this is on SQL Server 2005 express.
I tried the same using SQL Compact, but same result (tho different error):
only the DB is created (in this case a sdf file in the bin folder), but no tables
I get the error The format of the specified date or time datepart is not valid. [ String = 2012-04-19T13.21.04.364 ]
I think in both cases the problem lies in the line that EF wants to enter as first migration:
INSERT INTO [__MigrationHistory] ([MigrationId], [CreatedOn], [Model], [ProductVersion])
VALUES ('201204191321184_init', '2012-04-19T13.21.04.364', ...., '4.3.1');
Apparently the format with the . is wrong, at least in my locale, it should be with :
Is this a bug or what? It always worked with other datetime before.
UPDATE
I tried running it as explicit migration, and applying the migration with the -verbose flag set, and here is what I get:
PM> Update-Database -Verbose
Using NuGet project 'ConsoleApplication2'.
Using StartUp project 'ConsoleApplication2'.
Target database is: '|DataDirectory|ConsoleApplication2.ConsoleApplication1.BlogContext.sdf' (DataSource: |DataDirectory|ConsoleApplication2.ConsoleApplication1.BlogContext.sdf, Provider: System.Data.SqlServerCe.4.0, Origin: Convention).
Applying explicit migrations: [201204191356197_Initial].
Applying explicit migration: 201204191356197_Initial.
CREATE TABLE [Blogs] (
[BlogId] [int] NOT NULL IDENTITY,
[Name] [nvarchar](4000),
CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId])
)
CREATE TABLE [__MigrationHistory] (
[MigrationId] [nvarchar](255) NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[Model] [image] NOT NULL,
[ProductVersion] [nvarchar](32) NOT NULL,
CONSTRAINT [PK___MigrationHistory] PRIMARY KEY ([MigrationId])
)
[Inserting migration history record]
System.Data.SqlServerCe.SqlCeException (0x80004005): The format of the specified date or time datepart is not valid. [ String = 2012-04-19T13.56.45.437 ]
at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommandText(IntPtr& pCursor, Boolean& isBaseTableCursor)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery()
at System.Data.Entity.Migrations.DbMigrator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.ExecuteStatements(IEnumerable`1 migrationStatements)
at System.Data.Entity.Migrations.DbMigrator.ExecuteOperations(String migrationId, XDocument targetModel, IEnumerable`1 operations, Boolean downgrading)
at System.Data.Entity.Migrations.DbMigrator.ApplyMigration(DbMigration migration, DbMigration lastMigration)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ApplyMigration(DbMigration migration, DbMigration lastMigration)
at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.RunCore()
at System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
The format of the specified date or time datepart is not valid. [ String = 2012-04-19T13.56.45.437 ]
Update 2
I installed SQL Server Profiler, and profiled what is happening over there.
I executed all the statements one by one via query analyzer and the one that fails is, as already stated above, the insertion of the migration.
INSERT INTO [__MigrationHistory] ([MigrationId], [CreatedOn], [Model], [ProductVersion]) VALUES ('201204231416585_InitialCreate', '2012-04-23T14.16.59.038Z', ...., '4.3.1')
When changing the format of the datatime string from 2012-04-23T14.16.59.038Z to 2012-04-23T14:16:59.038Z the command went through, so I guess somehow EF is sending the datatime in format that is not compatible with my locale.
Thank you
Simone
Thanks to the ADO.NET Team, this was a bug in the Migration code.
Apparently they forgot to specify InvariantCulture when they generate code for a DateTime field, so it works on EN locale, but doesn't in other locales.
To fix this problem, waiting for an official fix, you should specify a custom SqlGenerator that overrides the Generate(DateTime defaultValue) method:
class FixedSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override string Generate(DateTime defaultValue)
{
return "'" + defaultValue.ToString("yyyy-MM-ddTHH:mm:ss.fffK", CultureInfo.InvariantCulture) + "'";
}
}
And then specify the new SqlGenerator in the Configuration class:
SetSqlGenerator("System.Data.SqlClient", new FixedSqlGenerator());
If you want to use it just in manual migrations that's enough, if you only need CodeFirst, you have to specify the configuration in the application startup code or in the DbContext.
Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Migrations.Configuration>());
HTH
I had the same problem with a brand new project, in my case I solved it by specifying the right culture in my web.config
<globalization enableClientBasedCulture="false" culture="en-US" />
Is there any way to use DataContext to execute some explicit SQL and return the auto-increment primary key value of the inserted row without using ExecuteMethodCall? All I want to do is insert some data into a table and get back the newly created primary key but without using LINQ (I use explicit SQL in my queries, just using LINQ to model the data).
Cheers
EDIT: Basically, I want to do this:
public int CreateSomething(Something somethingToCreate)
{
string query = "MyFunkyQuery";
this.ExecuteCommand(query);
// return back the ID of the inserted value here!
}
SOLUTION
This one took a while. You have to pass a reference for the OUTPUT parameter in your sproc in your parameter list of the calling function like so:
[Parameter(Name = "InsertedContractID", DbType = "Int")] ref System.Nullable<int> insertedContractID
Then you have to do
insertedContractID = ((System.Nullable<int>)(result.GetParameterValue(16)));
once you've called it. Then you can use this outside of it:
public int? CreateContract(Contract contractToCreate)
{
System.Nullable<int> insertedContractID = null; ref insertedContractID);
return insertedContractID;
}
Take heavy note of GetParameterValue(16). It's indexed to whichever parameter it is in your parameter list (this isn't the full code, by the way).
You can use something like this:
int newID = myDataContext.ExecuteQuery<int>(
"INSERT INTO MyTable (Col1, Col2) VALUES ({0}, {1});
SELECT Convert(Int, ##IDENTITY)",
val1, val2).First();
The key is in converting ##IDENTITY in type int, like Ben sugested.
If you insist on using raw sql queries, then why not just use sprocs for your inserts? You could get the identity returned through an output parameters.
I'm not the greatest at SQL, but I broke out LinqPad and came up with this. It's a big hack in my opinion, but it works ... kinda.
DataContext.ExecuteQuery<T>() returns an IEnumerable<T> where T is a mapped linq entity. The extra select I added will only populate the YourPrimaryKey property.
public int CreateSomething(Something somethingToCreate)
{
// sub out your versions of YourLinqEntity & YourPrimaryKey
string query = "MyFunkyQuery" + "select Convert(Int, SCOPE_IDENTITY()) as [YourPrimaryKey]";
var result = this.ExecuteQuery<YourLinqEntity>(query);
return result.First().YourPrimaryKey;
}
You'll need to modify your insert statement to include a SELECT ##Identity (SQL Server) or similar at the end.